0

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?

Sam Gregory
  • 317
  • 1
  • 7
  • i believe that this will help :) https://stackoverflow.com/questions/1129795/what-is-suppresswarnings-unchecked-in-java – Franklin Gualtiere Aug 08 '22 at 15:10
  • Due to type erasure Java cannot determine the type of the class at runtime. I usually get around this by adding the class as a parameter. Like this `private void someMethod(T object, Class class)` – Willempie Aug 08 '22 at 15:18
  • If you have a `List<...> ex = new ArrayList<>()` and do `getClass` on it, then what type is the `Class`? As for the `Class`, when you erase the generics information (aka when you evaluate this function at compile-time), what method would work for any possible input? – Rogue Aug 08 '22 at 15:19

3 Answers3

2

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.

Michael
  • 41,989
  • 11
  • 82
  • 128
  • This answer needs fixing - the type of `obj.getClass()` is in fact `Class extends T>` and not `Class>`. In other words, OP just needs to change their return type to `Class extends T>` instead of `Class`, and in general `Class` should always be written as `Class extends X>`, so that should never be a problem. – rzwitserloot Aug 08 '22 at 15:23
  • You end your answer with 'what you are expecting which is `Class extends T>`. No; it __is__ of type `Class extends T>`. OP's method sig demands a `Class`. They just need to fix that part. – rzwitserloot Aug 08 '22 at 15:24
  • @rzwitserloot "*In other words, OP just needs to change their return type to Class extends T> instead of Class*" No, you are wrong. https://ideone.com/GwJVdq – Michael Aug 08 '22 at 15:26
  • 2
    @rzwitserloot Not quite. "*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.*". Since `T` is erased to Object we would need to change return type to `Class extends Object>` to make it compile (which is same as `Class>`), not to `Class extends T>`. – Pshemo Aug 08 '22 at 15:28
0

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));
}
Joop Eggen
  • 107,315
  • 7
  • 83
  • 138
-2

You can salve that problem like this:

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