Generics in method signatures and casts?
Why does this work?
@SuppressWarnings("unchecked")
public <T> T getResult(Clas开发者_开发百科s annotated) {
return (T) getResult(annotated, new HashMap<String, Object>(0));
}
How does the compiler know how to cast the return value? It can't know it at compile time: it could be anything. Does it do it at runtime every time?
The type of T
in the generic method is inferred from the context of a call to the method, if possible.
String string = foo.getResult(Something.class);
If the type of T
can't be determined from the context (for example, if the result is passed directly as an argument to a method that is itself generic) you may have to specify the type yourself:
List<String> list = Arrays.asList(foo.<String>getResult(Something.class));
Generic methods may also use the types they declare in parameters, which can force the parameters and the result type to agree:
public <T> T getResult(Class<T> type) { ... }
String string = foo.getResult(String.class); // ok
String string = foo.getResult(Something.class); // not ok
At runtime, generics don't exist so it just tries to assign the result to whatever you're trying to assign it to... if it's the wrong type, you get a ClassCastException
.
It's easy for the VM at runtime because T
is simply Object
due to type-erasure. In other words this:
return (T)getResult(annotated, new HashMap<String, Object>(0));
Compiles to the same thing as this:
return (Object)getResult(annotated, new HashMap<String, Object>(0));
That cast is unchecked, which means that it is not checked - neither at compile time nor at runtime. To verify, compile and execute:
static <T> T magicCast(Class<T> clazz, Object o) {
return (T) o;
}
public static void main(String[] args) {
magicCast(Integer.class, "hello");
System.out.println("The magic cast always works.");
}
Of course, that is dangerous. Consider:
public class Test<T> {
public final T foo;
public Test(Object foo) {
this.foo = (T) foo;
}
public static void main(String[] args) {
Test<String> test = new Test<String>(42);
// after being passed through dozens of methods
test.foo.startsWith("hello"); // ClassCastException
}
}
That travesty of a variable holding an object that is not a subtype of the declared type is known as heap pollution. It is the reason why a compiler is mandated to emit an unchecked warning - unless somebody suppressed that warning.
It is therefore recommended to do a reflective cast where possible; for instance:
static <T> T magicCast(Class<T> clazz, Object o) {
return clazz.cast(o); // reflective cast, checked at runtime
}
Of course, if you know the class at compile time, you should use an ordinary cast, because that enables to compiler to performn additional sanity checks, allowing it to reject casts like
int x = (Integer) "hello";
which can never succeed.
Unfortunately you did not write how do you invoke this method. I believe that the code looks like:
obj.getResult();
In this case the answer is simple: the compiler adds casting to MyClass and if you decompile the byte code you see something like
(MyClass)obj.getResult();
The same happens if you invoke you method and assign it result to variable: MyClass result = obj.getResult();
By the way I think that it is a pity that you added annotation @SuppressWarining("unchecked") If T is of type "annotated" you should say:
public T getResult(Class annotated) { return (T) getResult(annotated, new HashMap(0)); }
This is the usual way to work with generics. See classes Collections, Arrays, AbstractList.toArray() etc.
int y=10;
int z=12;
精彩评论