Why is casting the class of a generic to Class

2020-03-23 02:09发布

问题:

I'm making a MethodPointer class in order to simulate the functionality of function pointers from C++. At first, I was doing everything with just Objects, but then I had a thought -- why not make it truly generic?

The problem came in this constructor, which attempted to call another constructor with the signature MethodPointer(Class<T> clazz, String methodName, Class<?> ... paramClasses):

public MethodPointer(T object, String methodName, Class<?> ... paramClasses) {
    this(object.getClass(), methodName, paramClasses);
    this.object = object;
}

I assumed this would work just fine, but I received the following compiler error:

The constructor MethodPointer<T>(Class<capture#1-of ? extends Object>,
String, Class<?>[]) is undefined

So, confused, I did this:

public MethodPointer(T object, String methodName, Class<?> ... paramClasses) {
    this((Class<T>) object.getClass(), methodName, paramClasses);
    this.object = object;
}

It now compiles, but I receive the following warning:

Unchecked cast from Class<capture#1-of ? extends Object> to Class<T>

I guess the problem is that I don't understand what Class<capture#1-of ? extends Object> means. I thought that since the type of T is inferred from the T object parameter that it would be necessary that calling object.getClass() returns a Class object of type Class<T>. Apparently this isn't the case, though. Can someone clear up my confusion?


Full class declaration and all constructors:

public class MethodPointer<T> {

    //Logger instance
    private static final Logger LOGGER = Logger.getLogger(MethodPointer.class);

    //Object fields
    private final Method method;
    private ArrayList<Object> args = new ArrayList<Object>();
    private T object = null;


    //Constructors
    public MethodPointer(Method method) {
        this.method = method;
    }

    public MethodPointer(Class<T> clazz, String methodName, Class<?> ... paramClasses) {
        Method theMethod = null;
        try {
            theMethod = clazz.getMethod(methodName, paramClasses);
        }
        catch(NoSuchMethodException nsme) {
            LogUtil.log(LOGGER, Level.ERROR, "Unable to find method " + methodName + " in " + clazz.getSimpleName(), nsme);
        }
        method = theMethod;
    }

    public MethodPointer(T object, String methodName, Class<?> ... paramClasses) {
        this((Class<T>) object.getClass(), methodName, paramClasses);
        this.object = object;
    }

回答1:

SLaks' answer points out that object.getClass() decays to Class<? extends Object>, to explain the compile error. But it's not safe to cast to Class<T>.

getClass "returns the runtime class" of the object it's called on. For example, if we're inside a MethodPointer<Number>, then object is-a Number but its runtime type could be Integer, Double, etc. That tells us that casting object.getClass() to Class<T> isn't safe because Class<Integer> and Class<Number> are different objects, representing different classes - a distinction that seems very relevant to the correctness of what you're trying to do.

So what's the solution? Well, don't cast. Insist on taking a Class<T> from the caller.



回答2:

The problem is that getClass() ignores type parameters.

The Javadocs say:

The actual result type is Class<? extends |X|> where |X| is the erasure of the static type of the expression on which getClass is called.

For example, new ArrayList<String>().getClass() returns a Class<? extends ArrayList>, not Class<? extends ArrayList<String>>.

This also means that getClass() called on a type parameter decays to a Class<? extends Object>.



回答3:

If you do not need an specified Class-type, you could use

Class<?>

for you parameters.

So you have an unbound Type for your classes.

And if you want to get an instance you could use it like this:

obj.getClass().getDeclaredConstructor(Class<?> parameterTypes)