In my current project, I have classes which are modeled like the following. At some point, a method like getReturnTypeForGetId() is called on classes A and B. Calling the method with A returns Integer as expected, but B returns Serializable.
What am I missing here? Am I getting bitten by some heinous erasure thing, or am I just missing out on some sort of generic context-clobbering?
EDIT: Adding an over-ridden getId() method to B fixes the problem, but I would still like to understand what I am running into.
import java.io.Serializable;
public class WeirdTester {
static interface Identifiable<T extends Serializable> {
T getId();
void setId(final T id);
}
static abstract class BaseEntity<T extends Serializable> implements Identifiable<T> {
private T id;
public T getId() { return id; }
public void setId(final T id) { this.id = id; }
}
static class A implements Identifiable<Integer> {
private Integer id;
public Integer getId() { return id; }
public void setId(final Integer id) { this.id = id; }
}
static class B extends BaseEntity<Integer> {}
@SuppressWarnings("unchecked")
private static <T extends Serializable, Q extends Identifiable<T>> Class<T> getReturnTypeForGetId(
final Class<Q> clazz) throws Exception {
return (Class<T>) clazz.getMethod("getId", (Class[])null).getReturnType();
}
public static void main(final String[] args) throws Exception {
System.out.println(getReturnTypeForGetId(A.class));
// CONSOLE: "class java.lang.Integer"
System.out.println(getReturnTypeForGetId(B.class));
// CONSOLE: "interface java.io.Serializable"
}
}
There are multiple
getIdmethods in the compiledAclass. You get a bridge method for the covariant return type (a “fiction” of the language not reflected in the virtual machine). The specification forClass.getMethodsays that it will return the method with the most specific return type (assuming that exists). It does this forA, but forBthe method is not overridden so javac avoids synthesizing an unnecessary bridge method.In fact, for this example all the information is still there in the class files. (Earlier I said it wasn’t erased. That’s not true, but erasure doesn’t mean that it isn’t there!) The generic information is however a little tricky to extract (it’ll be in
Identifiable.class.getGenericReturnType(),Identifiable.class.getTypeParameters(),BaseEntity.class.getGenericInterfaces,BaseEntity.class.getTypeParameters()andB.getGenericSuperclass(I think!)).Use
javapto see exactly what you have in the class files.