I wrote a method to substitute for View.findViewById(int id)
, But I got a warning:
Is there any problem in this method ? Or how to avoid it ?
I wrote a method to substitute for View.findViewById(int id)
, But I got a warning:
Is there any problem in this method ? Or how to avoid it ?
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 Object
s). 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.)