简体   繁体   中英

Generics in method signatures and casts?

Why does this work?

@SuppressWarnings("unchecked")
public <T> T getResult(Class 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;

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM