Java: T obj; type of obj.getClass() is Class<?> and not Class<? extends T>. why?
Asked Answered
B

4

10

In such a function:

<T> void foo(T obj)

The type of obj.getClass() is Class<?> and not Class<? extends T>. Why?

The following code works fine:

String foo = "";
Class<? extends String> fooClass = foo.getClass();

So the signature of T#getClass() seems to return a Class<? extends T>, right?

Why is the signature different if T really is a generic?

To overcome the problem (and to make it more clear what I wander about), I have implemented this function:

@SuppressWarnings("unchecked") static <T> Class<? extends T> classOf(T obj) {
    return (Class<? extends T>) obj.getClass();
}

Again the question: Why is the cast needed here and not in the String case? And why is the SuppressWarnings needed? Isn't it always clear from the code that it will always be able to safely do this cast?

Is there any way I can get a Class<? extends T> from obj? If yes, how? If not, why not?

One way would be to use classOf. That would be safe, right? If that is always safe and gives a safe way to really get a Class<? extends T> (instead of a Class<?>), why is there no such function in Java? Or is there?


How about that case:

<T> void bar(T[] array)

array.getClass().getComponentType() again returns a Class<?> and not a Class<? extends T>. Why?

I have implemented this function:

@SuppressWarnings("unchecked") static <T> Class<? extends T> classOf(T[] array) {
    return (Class<? extends T>) array.getClass().getComponentType();
}

Is this again safe to use?


To clarify more what I wonder about. Consider this demo code:

static interface I<T> {
    Class<? extends T> myClass();
}

static class A implements I<A> {
    public Class<? extends A> myClass() {
        return this.getClass();
    }
}

static <T> void foo(I<T> obj) {
    Class<? extends T> clazz = obj.myClass(); // this works
}

This works fine. But the same does not for Object#getClass().

Why wasn't it possible for example to have a generic interface like ClassInstance<T> with the function getClass() and every Java Object automatically implementing this? This would have exactly those improvements I am talking about over the solution to have it extending from a non-generic base class Object.

Or having Object as a generic class:

static abstract class Object<T> {
    abstract Class<? extends T> myClass();
}

static class B extends Object<B> {
    public Class<? extends B> myClass() {
        return this.getClass();
    }
}

static <T> void bar(Object<T> obj) {
    Class<? extends T> clazz = obj.myClass(); // this works
}

Now think of myClass() as getClass() and think about that the compiler would automatically add that to every class. It would have resolved a lot of those casting issues.

The main question I am talking about is: Why wasn't it made like this?


Or to put it again in different words: Here, I describe in more detail the solution of such classOf function which overcomes the problem. Why wasn't it made like this, i.e. why is the original function not like this?

(I don't really want to get an answer like: the way Java works right now, i.e. extending from a non-generic Object which defines this function, makes this not possible. I am asking why it wasn't solved somehow differently so that it would have been possible.)

Bobcat answered 24/10, 2010 at 23:11 Comment(3)
To answer your edit... that wouldn't compile because even if A is a child of B, Class<A> is not a child of Class<B>. You would have incompatible return types. - The only way you'd get it to work is if everything returned Class<? extends Object> which is basically what it is now.Preciado
@CurtainDog: Both examples do compile (well, change the name Object in the second example to sth. else). But my question is anyway not really about how could I do it but more about why it is like that. It is already possible (the way I showed for example) to make getClass() return the right type. So why wasn't it made like that?Bobcat
I think this was simply overseen when they patched the compiler to return Class<? extends X> from x.getClass().Hector
I
6

The basic problem is that getClass() doesn't return the class because its defined at the Object level. i.e. it is mearly defined as a class which extends object. They could have defined getClass() like.

Class<this> getClass() { /**/ }

but instead its

Class<?> getClass()

which means generics has no understanding of what getClass returns.

Incommensurable answered 24/10, 2010 at 23:29 Comment(3)
"They could have defined [...]". Actually, your answer gets close to what I actually wanted to know. The why in the question. Why wasn't it done like this (or in a similar way) so that it would have been possible what I want (because Java itself actually would allow what I want -- see my examples).Bobcat
The reason why is Java doesn't have a mechanism in generics to return this type. You would have to ask the designers of Java for why that is.Yukikoyukio
I have implemented interfaces which would have been very useful to have a return type of this. Instead I had to use convariant return types of loads of methods (hundreds)Incommensurable
F
4

In Java generics are just a source level tool for safer development.

The JVM does not know anything about generics. The Java compiler throws away this information, and all generic types are indeed just Object references at runtime. To compensate for that the compiler inserts the necessary casts. This procedure is called Type Erasure (Google!).

List<String> x = new ArrayList<String>();
x.add("hello");
String hello = x.get(0);

becomes the following at runtime

List x = new ArrayList();
x.add("hello");
String hello = (String) x.get(0);

To solve your problem you could try to investigate the individual elements in your array (arr[0].getClass()).

Forth answered 24/10, 2010 at 23:24 Comment(4)
"To solve your problem you could try to investigate the individual elements in your array". So long as you have elements in your array! But +1 for the discourse.Jobey
But doesn't the array itself must also know what it is? It can throw a ArrayStoreException, so the information must be there somewhere. And if it is a T[] at compile time, the compiler would be able in theory to safely get Class<T> somehow.Bobcat
Albert: Are you sure it's not just an Object[]?Canoodle
Minor nitpick: there are some remnants of generic type info left by compiler in class bytecode. Specifically, field generic type, method generic types (params, return type) and supertype generic type substitutions ARE retained and accessible. But it is true that Object instances have no generic info, only class descriptions.Armillas
P
3

There are a couple of not totally accurate answers here. Generics are indeed implemented using type erasure, however this does not mean that all type information is lost. The compiler will erase the type to the lowest bound it can.

So <T extends String> gets erased to String; this is why getClass on a String returns Class<? extends String>. Your unbounded <T> however gets erased to Object; and so getClass returns Class<? extends Object>, i.e. Class<?>

Generics are complex, and they don't always do what you want, but there are ways to work around many things (by improving your bounds, accessing runtime type information via reflection, and passing class objects around). Type erasure is actually a pretty clever solution, undeserving of much of the bad press it has received.

Preciado answered 25/10, 2010 at 1:56 Comment(1)
Ah, this is interesting! But it still makes me wonder why it is like that (because I don't see a specific reason to have such a restriction on getClass() even with type erasure). I extended my original question to show what I mean.Bobcat
A
0

Because of type erasure, the runtime doesn't keep type information for generic arrays. In effect, the JVM internally treats all generic arrays as if they are an Object[].

If you want to get a runtime type, your best option may simply be to call getClass() on the first item in the array. You'll obviously need to find a way to handle the empty case, and the case where the contained objects are of multiple types, etc.

Apeman answered 24/10, 2010 at 23:22 Comment(3)
But array.getClass().getComponentType() would return me the real class, wouldn't it? So it actually knows about the array type at runtime.Bobcat
It would return the real class if you created an array of a specific type, e.g. new Integer[] {1,2,3,4}. But not if you created a generic array, e.g. a T[] in a class SomeCollection<T> - you'd just get an array of java.lang.ObjectApeman
Yes, if you call that example function (from my original question), it must be a specific type. I am also assuming this case here but I think it is even not possible to not have that. It is also not possible at all to create a generic array.Bobcat

© 2022 - 2024 — McMap. All rights reserved.