Why is using Collection<String>.class illegal?
I am puzzled by generic开发者_StackOverflow社区s. You can declare a field like:
Class<Collection<String>> clazz = ...
It seems logical that you could assign this field with:
Class<Collection<String>> clazz = Collection<String>.class;
However, this generates an error:
Syntax error on token ">", void expected after this token
So it looks like the .class
operator does not work with generics. So I tried:
class A<S> { }
class B extends A<String> { }
Class<A<String>> c = B.class;
Also does not work, generates:
Type mismatch: cannot convert from
Class<Test.StringCollection> to Class<Collection<String>>
Now, I really fail to see why this should not work. I know generic types are not reified, but in both cases it seems to be fully type safe without having access to runtime generic types. Anybody an idea?
Generics are invariant.
Object o = "someString"; // FINE!
Class<Object> klazz = String.class; // DOESN'T COMPILE!
// cannot convert from Class<String> to Class<Object>
Depending on what it is that you need, you may be able to use wildcards.
Class<? extends Number> klazz = Integer.class; // FINE!
Or perhaps you need something like this:
Class<List<String>> klazz =
(Class<List<String>>) new ArrayList<String>().getClass();
// WARNING! Type safety: Unchecked cast from
// Class<capture#1-of ? extends ArrayList> to Class<List<String>>
As for the non-reified at run-time case, you seem to have a good grasp, but here's a quote anyway, from the Java Tutorials on Generics, The Fine Print: A Generic Class is Shared by All Its Invocations:
What does the following code fragment print?
List <String> l1 = new ArrayList<String>(); List<Integer> l2 = new ArrayList<Integer>(); System.out.println(l1.getClass() == l2.getClass());
You might be tempted to say
false
, but you'd be wrong. It printstrue
, because all instances of a generic class have the same run-time class, regardless of their actual type parameters.
That is, there's no such thing as List<String>.class
or List<Integer>.class
; there's only List.class
.
This is also reflected in the JLS 15.8.2 Class Literals
A class literal is an expression consisting of the name of a class, interface, array, or primitive type, or the pseudo-type void, followed by a
.
and the tokenclass
.
Note the omission of any allowance for generic type parameters/arguments. Furthermore,
It is a compile time error if any of the following occur:
- The named type is a type variable or a parameterized type, or an array whose element type is a type variable or parameterized type.
That is, this also doesn't compile:
void <T> test() {
Class<?> klazz = T.class; // DOESN'T COMPILE!
// Illegal class literal for the type parameter T
}
Basically you can't use generics with class literals, because it just doesn't make sense: they're non-reified.
I agree with the other answers, and would like to explain one point further:
Class objects represent classes that are loaded into the JVM memory. Each class object is actually an in-memory instance of a .class
file. Java generics are not separate classes. They are just a part of the compile-time type-checking mechanism. Therefore, they have no run-time representation in a class object.
There seems to be a lack in class literals in Java, there is no way to create class literals with generic information while this can be useful in certain cases. Therefore, the following code cannot be called because it is impossible to provide the class literal
class A<S> {}
<S> A<S> foo( Class<A<S>> clazz ) {}
A<String> a = foo( A<String>.class ) // error
However, my main problem was I could also not call it with a class B that extended A. This was caused by the invariance restrictions. This was solved by using a wildcard:
class A<S> {}
class B extends A<String> {}
<S> A<S> foo( Class<? extends A<S>> clazz ) { return null; }
void test () {
A<String> s = foo( B.class );
}
That said I have not found a reason what the underlying reason is that Class<A<S>>.class
is invalid. Neither erasure nor bounds seem require that this is invalid.
精彩评论