15

I noticed something while I was derping around with generics. In the example below, doStuff1 compiles but doStuff2 doesn't:

public <T extends Foo> void doStuff1(T value) {
    Class<? extends Foo> theClass = value.getClass();
}

public <T extends Foo> void doStuff2(T value) {
    Class<? extends T> theClass = value.getClass();
}

So, I looked up the documentation for Object.getClass() and found this:

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.

This made me a bit curious. Why is getClass() designed this way? I can understand converting types to their raw classes if applicable, but I see no obvious reason why they'd necessarily have to make it also kill off T. Is there a specific reason why it also gets rid of it, or is it just a general "let's just get rid of everything because it's easier; who would ever need it anyway" approach?

BambooleanLogic
  • 6,088
  • 2
  • 26
  • 51
  • 1
    Figuring this out is going to make my head hurt; I hate calculating generic type bounds even when it's not so late. I suggest starting at [the JLS for `Object`](http://docs.oracle.com/javase/specs/jls/se7/html/jls-4.html#jls-4.3.2) and looking at the search rules; this is probably an artifact of how the JVM does its sorta-staticky magic to reflect on the object's runtime class. – chrylis -cautiouslyoptimistic- Aug 09 '13 at 10:39

5 Answers5

10

If getClass() returns Class<? extends X>, nothing really bad can happen; actually it'll help a lot of use cases.

The only problem is, it is not theoretically correct. if an object is an ArrayList<String>, its class cannot be Class<ArrayList<String>> - there is no such class, there is only a Class<ArrayList>.

This is actually not related to erasure. If one day Java gets full reified types, getClass() should still return Class<? extends |X|>; however there should be a new method, like getType() which can return a more detailed Type<? extends X>. (though, getType may conflict with a lot of existing classes with their own getType methods)

For the timing being, since Class<? extends X> might be useful in a lot of cases, we can design our own method that does that

static <X> Class<? extends X> myGetClass(X x){ ... }

but it's understandable they wouldn't put this kind of hack in standard lib.

ZhongYu
  • 18,232
  • 5
  • 28
  • 55
  • 1
    What would you put in place of the “`...`”? An unchecked cast? – 5gon12eder Mar 29 '16 at 02:01
  • @5gon12eder -- yes. the method is fundamentally unsafe; the caller must make sure that it makes sense in particular use cases. – ZhongYu Apr 18 '16 at 22:35
  • Does this means `static Class extends X> myGetClass(X x){ return (Class extends X>) x.getClass(); }` would only be safe if X is not a generic class, example List ? – rkouye Nov 07 '18 at 16:13
0

Consider the following program:

var a = new ArrayList<String>();
var b = new ArrayList<Integer>();
var aType = a.getClass();        
var bType = b.getClass();        

if (aType == bType) {
  ...
}

If we execute this, aType and bType will contain the same runtime class object (since all instances of a generic type share the same runtime class), and the body of the if statement will execute.

It will also compile just fine. In particular, the declared types of aType and bType are both Class<? extends ArrayList>, and comparing two references of compatible types makes sense to the compiler.

However, if getClass were defined to return Class<? extends T> instead, the compile time types of aType would be Class<? extends ArrayList<String>, and the type of bType would be Class<? extends ArrayList<Integer>>. Since Java defines generics as invariant, these are inconvertible types, and the compiler would thus be required to reject the comparision aType == bType as nonsensical, even though the runtime thinks it true.

Since fixing this would have required a major extension to the java type system, declaring getClass to return the erasure was likely seen as the simpler option, even though it causes counter-intuitive behavior in other cases, such as the one you encountered. And of course, once defined that way, it could no longer be changed without breaking API ...

To work around the workaround, you can use an unchecked cast:

@SuppressWarnings("unchecked")
<T> Class<? extends T> getTypedClassOf(T t) {
   return (Class) t.getClass();
}

Of course, this means that you might need to resort to unchecked casts to get the compiler to understand that class objects of inconvertible types might be identical after all ...

meriton
  • 61,876
  • 13
  • 96
  • 163
-1

You cannot be sure that value is actually a T, it could also be a subclass of T. getClass() does return the "real" class of value, but you can't be sure that's T, therfore it has to read Class<? extends T>! This has nothing to do with the fact that T is a type parameter.

[Edit] Ok not quite, i didn't realize method2 doesn't compile. I think this is a compiler issue. I get

Type mismatch: cannot convert from Class<capture#1-of ? extends Foo> to Class<? extends T>

I think the compiler only realizes that the T object is a Foo. The compiler should know getClass() returns some Class<? extends T> but only seems to know that it returns some Class<? extends Foo>. Does that mean Foo is the erasure of the static type of T?

kutschkem
  • 6,194
  • 3
  • 16
  • 43
  • 1
    Uh, and your point? `Class extends T>` is the very example I used of code that I think _should_ be valid but isn't. The question is about why getClass() is (seemingly) overprotective of the type outputted. – BambooleanLogic Aug 09 '13 at 10:51
  • Uh, my bad i didnt read carefully enough to see that your second method doesn't compile. I somehow thought the question was about why the return value is Class extends T> instead of Class... I think that's really weird, now that i understand the question. I would guess it has something to do with the compiler, and nothing with getClass(). Maybe the Compiler is erasing T to Object, but knows in the first case that the static type needs to be some Foo? Then in the second case it would read Class = FooObject.getClass(), which seems wrong. ?! – kutschkem Aug 09 '13 at 11:19
-1

Very good question.

The whole problem with java generics is that they didn't exist in old virtual machines. Sun decided to add generic support while existing virtual machine did not support them.

This could be a big problem, because when they release the new Java, old virtual machines would NOT be able to run any code written for the newest version of Java, and they would not have been able to legacy-support old virtual machines.

So, they decided to implement generics in a way that they could also be run in old virtual machines. They decided to ERASE THEM FROM BYTECODE.

So the whole story is about back-support of jvms.

EDIT: But this had maybe nothing to do with the question. See kutschkem's answer!

Also, my guess is that in the second case, the cast is Class<? extends List> to Class<? extends T>. This cast is only possible if List directly extends T (since it is an implicit cast). But its the other way around, T extends List.

I think its the same thing as saying:

String s = "s";
Object o = s;

In the above case, the cast is possible because String extends Object. For the inverse, you would need an explicit cast.

If you add an explicit cast to your program, it compiles:

public <T extends List> void doStuff2(T value) {
   Class<? extends T> theClass = (Class<? extends T>) value.getClass();
}
Lake
  • 3,704
  • 24
  • 35
  • 1
    There's a case to be made that this backward compatibility has caused more trouble than breaking it would have, but that's what Sun decided. ;-) – chrylis -cautiouslyoptimistic- Aug 09 '13 at 10:30
  • 100% agreed. This is probably the only thing that I really hate about Java^^ – Lake Aug 09 '13 at 10:30
  • This question is not about type erasure, it's about Class Hierarchies. see my answer. Even if T was not a type parameter, but any non-final Class, then getClass() still return a Class extends T> – kutschkem Aug 09 '13 at 10:30
  • @kutschkem You're right. I should think about the questions before jump to write an answer assuming that i understood the question ^^; – Lake Aug 09 '13 at 10:49
  • @Lake you might be partly right after all, i didn't realize method2 doesn't compile :-/ I think it has to do with erasure in combination with bounds, maybe? – kutschkem Aug 09 '13 at 11:24
  • @kutschkem by reading your answer, i thought that maybe the cause is that Class extends List> cannot be converted to Class extends T>.. which is because T is not necessarely of type List exactly, but can be of any type that inherits from List. It made sense to me. – Lake Aug 09 '13 at 11:37
-2

The guava reflection tools provide some tools to work around this limitation and help you to get the type (so class) of a bounded type.

As said in the first line of this explanation page:

Due to type erasure, you can't pass around generic Class objects at runtime -- you might be able to cast them and pretend they're generic, but they really aren't.

Guava provides TypeToken, which uses reflection-based tricks to allow you to manipulate and query generic types, even at runtime.

The idea is to get (or create) a TypeToken of the bounded type and then you can ask for its type.

I figured that this answer is a bit off topic, because it doesn't answer the question directly (the 'why' part), but it can fix the non compilable method, it can lead to code able to solve the question and to give you the answer to the why part, and will certainly help other people looking for 'Java: - how to -getClass() of bounded type"

Guillaume
  • 5,208
  • 10
  • 43
  • 77