42

I am puzzled by generics. 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?

Peter Kriens
  • 15,196
  • 1
  • 37
  • 55

3 Answers3

36

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 prints true, 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 token class.

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.

Community
  • 1
  • 1
polygenelubricants
  • 376,812
  • 128
  • 561
  • 623
1

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.

Little Bobby Tables
  • 5,261
  • 2
  • 39
  • 49
  • Generics are constraints during compilation and are mostly not available during runtime. However, I am not looking at having the generic type argument available in runtime. For example, ArrayList.class.newInstance() seems perfectly acceptable. I fail to find where this model fails, all the examples I can think do not introduce the need to have runtime class information of the type parameters. I am looking for an example where the class literal would fail because it would require runtime information ... – Peter Kriens May 01 '10 at 12:26
  • For full type information at compile time you can look at the Mirrors API - I don't remember the link right now. – Little Bobby Tables May 01 '10 at 17:19
  • I do not think this is about type (in the class sense) information at compile time, this is only about type constraints, i.e. type parameters. – Peter Kriens May 03 '10 at 06:06
0

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.

Peter Kriens
  • 15,196
  • 1
  • 37
  • 55