29

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?

durron597
  • 31,968
  • 17
  • 99
  • 158
Heisenberg
  • 3,153
  • 3
  • 27
  • 55

3 Answers3

39

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.

durron597
  • 31,968
  • 17
  • 99
  • 158
  • Are you sure that "The erasure of every other type is the type itself." is preventing type erasure of anonymous classes? That sentence in JLS means that erasure, for example, of String is String. Anonymous class is just a convenient way to have immediate superclass from where getGenericSuperclass can get type information – Ivan Sep 20 '21 at 21:28
  • @Ivan it also means that the erasure of `TypeReference() {}` is `TypeReference() {}`. It means both – durron597 Sep 20 '21 at 22:43
  • But getGenericSuperclass works not only for anonymous classes but for normal extends too. So erasure doesn't work for superclases too? I'm a little bit confused, so my questions can be confusing too – Ivan Sep 21 '21 at 07:44
  • @Ivan Maybe you should try it and see what happens. `class MyAwesomeList extends java.util.ArrayList { }` `new MyAwesomeList().getClass().getGenericSuperClass()` – durron597 Sep 21 '21 at 19:39
  • Probably I've got your point: getGenericSuperclass shows actual type parameter that was provided in class 'extends' statement, so we will get T in your example. Anonymous class implicitly extends super class so why we can get actual type on its instance. But I still can't agree that `The erasure of every other type is the type itself.` plays a role here – Ivan Sep 22 '21 at 07:24
  • @Ivan in all the other cases, the the erasure removes the generic type. This is the only one that keeps it. – durron597 Sep 27 '21 at 15:58
11

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.

Louis Wasserman
  • 191,574
  • 25
  • 345
  • 413
2

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.

mightyWOZ
  • 7,946
  • 3
  • 29
  • 46