Home > other >  Java required type <T> provided <?> generic compilation error
Java required type <T> provided <?> generic compilation error

Time:08-09

How is Java unable to interpret this code:

    private <T> Class<T> getClass(T object) {
       return object.getClass();
    }

I'm trying to visualise this and when I substitute in for an exmaple

    private Class<String> getClass(String s) {
        return s.getClass();
    } 

I get the same sort of error: required type <String> provided type <? extends String>. I've seen code that gets around this error by doing:

        @SuppressWarnings("unchecked")
        Class<T> clazz = (Class<T>) object.getClass();

What does this mean? What am I not understanding about this generics problem?

CodePudding user response:

getClass follows some specific rules that are a bit unusual. The declared return type of Object::getClass is simply Class<?>, i.e. a class of some type. But as per the docs

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.

The erasure for T in your example is simply Object, so the return is Class<? extends Object>. Everything extends Object, so that's the same as Class<?>.

When the parameter is the specific type String, the erasure is also String, and the result is Class<? extends String>.

Oracle tutorial on type erasure

I guess what happens under the hood is that the compiler effectively "overrides" Object::getClass with a more specific return type. Child classes are allowed to return more specific types than their parents, but not less specific. Consider this valid code:

class Foo {
    public Class<? extends List<?>> foo() { return null; } 
}

class Bar extends Foo {
    @Override
    public Class<? extends ArrayList<?>> foo() { return null; }
}

Because of how this is implemented, T obj; obj.getClass(); will return Class<?>, rather than what you're expecting which is Class<? extends T>.

In your case, the cast is safe.

CodePudding user response:

As types are still erase you just have an Object object.

Class<?> type = object.getClass();

It is the other way around, one may use:

private <T> T getObject(Object object, Class<T> type) {
   return type.cast(object);
}

The above does a dynamic runtime cast. This might be useful:

private final Map<Class<?>, Object> singletonsByType = new HashMap<>();

public <T> void store(Class<T> type, T object) {
    singletonsByType.put(type, object);
}

public <S> S lookup(Class<S> type) {
    return type.cast(singletonsByType.get(type));
}

CodePudding user response:

You can salve that problem like this:

private <T> Class<T> getClass(T object) {
   return (Class<T>)object.getClass();
}
  • Related