Search code examples
javaandroidgenericsfindviewbyid

java generic method warning "Unchecked cast"


I wrote a method to substitute for View.findViewById(int id), But I got a warning: enter image description here

Is there any problem in this method ? Or how to avoid it ?


Solution

  • I believe this is the reason for the warning: Java allows downcasting, that is, casting an object of type X to a subclass of X. However, this requires a check at runtime. For example:

    Object x1 = <... something that returns an object that may be a String ...>;
    String x2 = (String)x1;
    

    The cast will try to treat x1 as a String. But x1 can be any Object. It may or may not be a String. The cast works only if x1 is actually a String; otherwise you get a ClassCastException at runtime.

    The problem in your code is that T is a subclass of View, which means that the cast would ordinarily cause the same kind of check to be performed at runtime. However, because of type erasure, the program doesn't actually have information about the class of T at this point in the code, which means that it can't perform the check. So you get a warning about the program using an unchecked operation. The program cannot guarantee that, when this method returns, the returned object will actually be an instance of class T.

    I tried some test cases and found that the check often takes place on the statement that calls the generic method. Suppose View2 extends View, and you want to use find in a way that returns a View2. Then:

    View2 v = YourClass.<View2>find(x, id);
    

    If the object found by findViewById isn't really a View2, you'll get a ClassCastException. But:

    View v = YourClass.<View2>find(x, id);
    

    Suppose findViewById returns some other view. Based on my tests, this won't throw an exception, even though the type parameter is View2; since this gets assigned into a View, which will work fine if the result is some other view, no check for View2 ever occurs.

    String s = YourClass.<View2>find(x, id).toString();
    

    Suppose findViewById returns some other view. When I tried this, I thought that it wouldn't throw an exception, because the other view would also have toString() (like all Objects). But this did throw ClassCastException.

    I don't know if there's a good way to fix it. One possibility is to add a third parameter to find to denote the class of T, and use its cast method:

    public static <T extends View> T find(View view, int id, Class<T> theClass) {
        return theClass.cast(view.findViewById(id));
    }
    
    View v = YourClass.find(x, id, View2.class);
    

    This works--it throws an exception from the cast() method if findViewById returns the wrong class. However, adding a third parameter to every use might not be appealing, even though it may allow you to eliminate an explicit generic type parameter from the call.

    I think this is a case where it's OK to ignore the warning, and use @SuppressWarnings("unchecked") to stop the warning from showing up, if you're OK with the possibility that sometimes the program might continue instead of throwing an exception when findViewById returns the wrong kind of view.

    (Disclaimer: My tests were conducted using Java 8. I didn't use anything that wouldn't be valid in Java 7. But if Android still hasn't fully implemented Java 7, some of what I wrote could be wrong.)