Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generic Inheritance and Calling GetMethod().getReturnType()

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"
    }
}
like image 521
Ryan Ransford Avatar asked Oct 28 '11 15:10

Ryan Ransford


2 Answers

There are multiple getId methods in the compiled A class. You get a bridge method for the covariant return type (a "fiction" of the language not reflected in the virtual machine). The specification for Class.getMethod says that it will return the method with the most specific return type (assuming that exists). It does this for A, but for B the 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() and B.getGenericSuperclass (I think!)).

Use javap to see exactly what you have in the class files.

like image 52
Tom Hawtin - tackline Avatar answered Oct 28 '22 14:10

Tom Hawtin - tackline


In class A you override getId to return Integer.

In class B you don't override getId, so the getId method in B is the one from BaseEntity. Because of erasure, that one returns Serializable.

like image 40
Laurence Gonsalves Avatar answered Oct 28 '22 14:10

Laurence Gonsalves