How does Gson TypeToken work?

I understand that in Java contrary to, for example, C# generics are compile-time feature and is removed via type erasure. So, how does Gson's TypeToken really work? How does it get the generic type of an object?


Solution 1:

It's a trick!

From §4.6 of the JLS (emphasis mine):

Type erasure is a mapping from types (possibly including parameterized types and type variables) to types (that are never parameterized types or type variables). We write |T| for the erasure of type T. The erasure mapping is defined as follows:

The erasure of a parameterized type (§4.5) G is |G|.

The erasure of a nested type T.C is |T|.C.

The erasure of an array type T[] is |T|[].

The erasure of a type variable (§4.4) is the erasure of its leftmost bound.

The erasure of every other type is the type itself.

Therefore, if you declare a class with an anonymous subclass of itself, it keeps it's parameterized type; it's not erased. Therefore, consider the following code:

import java.lang.reflect.ParameterizedType;
import java.util.Arrays;
import java.util.HashMap;

public class Erasure<T>
{
    public static void main(String...strings) {
      Class<?> foo = new Erasure<HashMap<Integer, String>>() {}.getClass();
      ParameterizedType t = (ParameterizedType) foo.getGenericSuperclass();
      System.out.println(t.getOwnerType());
      System.out.println(t.getRawType());
      System.out.println(Arrays.toString(t.getActualTypeArguments()));
    }
}

This outputs:

null
class Erasure
[java.util.HashMap<java.lang.Integer, java.lang.String>]

Notice that you would get a ClassCastException if you did not declare the class anonymously, because of erasure; the superclass would not be a parameterized type, it would be an Object.

Solution 2:

Java's type erasure applies to individual objects, not classes or fields or methods. TypeToken uses an anonymous class to ensure it keeps generic type information, instead of just creating an object.

Solution 3:

Gson TypeToken utilizes Neal Gafter's super type tokens pattern. this pattern is based on the Class#getGenericSuperclass() method, from docs

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.

This essentially means if you have a class extending a Parameterized class, then you can get actual type parameters to super class as

((ParameterizedType)myClassObj.getGenericSuperClass()).getActualTypeArguments()

So when you create a type token in Gson as

Type someTypeToken = new TypeToken<Collection<Integer>>(){};

// This essentially is same as defining
class AnonClass extends TypeToken<Collection<Integer>>{}

Now you can easily get Type parameters to super type (TypeToken) as specified above.