11

In Neal Gafter's "super type token" pattern (http://gafter.blogspot.com/2006/12/super-type-tokens.html), an anonymous object was used to pass in the parameterized type :

class ReferenceType<T>{}

/* anonymous subclass of "ReferenceType" */
ReferenceType<List<Integer>> referenceType = new ReferenceType<List<Integer>>(){

};
Type superClass = b.getClass().getGenericSuperclass();
System.out.println("super type : " + superClass);
Type genericType = ((ParameterizedType)superClass).getActualTypeArguments()[0];
System.out.println("actual parameterized type : " + genericType);

Then result is :

super type : com.superluli.test.ReferenceType<java.util.List<java.lang.Integer>>
actual parameterized type : java.util.List<java.lang.Integer>

My question is, what the magic does the anonymous object "referenceType" do to make it work? If I define a explicit subclass of "ReferenceType" and use it instead of the anonymous style, it doesn't as expected.

class ReferenceType<T>{}
class ReferenceTypeSub<T> extends ReferenceType<T>{}

/* explicitly(or, named) defined subclass of "ReferenceType" */
ReferenceType<List<Integer>> b = new ReferenceTypeSub<List<Integer>>();
Type superClass = b.getClass().getGenericSuperclass();
System.out.println("super type : " + superClass);
Type genericType = ((ParameterizedType)superClass).getActualTypeArguments()[0];
System.out.println("actual parameterized type : " + genericType);

The result is :

super type : com.superluli.test.ReferenceType<T>
actual parameterized type : T
superluli
  • 113
  • 9
  • 1
    It's unclear which part (specifically) of the article you don't understand as it explains this pretty well. The class he presents is abstract because due to type erasure (which is explained in the article) ... that's how it has to work. That's the entire point of the article. Your second example is basically the entire point - that can't work in Java due to type erasure. – Brian Roach May 01 '14 at 04:22
  • Hi Brian, I think it has nothing to do with type erasure, please correct me if I'm wrong. Type erasure means during compiling the generic types are removed from source codes and JVM won't use them for type checking in runtime. But on the other hand none of those information are "actually lost" : they are still saved in the .class files during compiling, hence loaded into class object pools by the class loader. And Java reflection provides interfaces to let us retrieve those information. – superluli May 01 '14 at 06:47
  • 1
    So, in both examples the types are erased. The actual difference is in 1st example things like "ReferenceType>" was saved in the anonymous class's .class file, while in the 2nd example "ReferenceType" was saved. – superluli May 01 '14 at 06:48
  • 1
    Basically, you're one level too deep. You can only get the generic type from the direct superclass, and only if the subclass provides a concrete type in the declaration. Type erasure will lead you to drink (trust me). – Brian Roach May 01 '14 at 06:51

1 Answers1

14

This

ReferenceType<List<Integer>> referenceType = new ReferenceType<List<Integer>>(){

is equivalent to

public class AnonymousReferenceType extends ReferenceType<List<Integer>> {}
...
ReferenceType<List<Integer>> referenceType = new AnonymousReferenceType();

The hack works around Class#getGenericSuperclass() which states

Returns the Type representing the direct superclass of the entity (class, interface, primitive type or void) represented by this Class. If the superclass is a parameterized type, the Type object returned must accurately reflect the actual type parameters used in the source code. The parameterized type representing the superclass is created if it had not been created before. See the declaration of ParameterizedType for the semantics of the creation process for parameterized types. If this Class represents either the Object class, an interface, a primitive type, or void, then null is returned. If this object represents an array class then the Class object representing the Object class is returned.

In other words, the superclass of AnonymousReferenceType is a ParameterizedType representing ReferenceType<List<Integer>>. This ParameterizedType has an actual type argument and that is a List<Integer> which is what appears in the source code.


In your second example, which is not the same as your first,

class ReferenceType<T>{}
class ReferenceTypeSub<T> extends ReferenceType<T>{}

the super class (super type) of ReferenceTypeSub is a ReferenceType<T> which is a ParameterizedType where the actual type argument is a TypeVariable named T, which is what appears in the source code.


To answer your question, you don't need an anonymous class. You just need a sub class which declares the type argument you want to use.

Sotirios Delimanolis
  • 252,278
  • 54
  • 635
  • 683
  • 1
    Then the information about the parameterized type "List" will be saved in the AnonymousReferenceType.class file. The question is, why in the 2nd example, a literal "T" was saved there not the actual type "List" I declared? Also please refer to the comments of the original question where I posted my own thinking but not sure if its correct. – superluli May 01 '14 at 06:58
  • 1
    After a while now I understand it. Just like what you said it all comes from the class declaration. In the 1st example ""List" is in the declaration of the anonymous class. while in the 2nd, it's T. – superluli May 01 '14 at 07:20