Java Top

I just announced the new Learn Spring course, focused on the fundamentals of Spring 5 and Spring Boot 2:

>> CHECK OUT THE COURSE

1. Overview

In this tutorial, we're going to get familiar with super type tokens and see how they can help us to preserve generic type information at runtime.

2. The Erasure

Sometimes we need to convey particular type information to a method. For example, here we expect from Jackson to convert the JSON byte array to a String:

byte[] data = // fetch json from somewhere
String json = objectMapper.readValue(data, String.class);

We're communicating this expectation via a literal class token, in this case, the String.class. 

However, we can't set the same expectation for generic types as easily:

Map<String, String> json = objectMapper.readValue(data, Map<String, String>.class); // won't compile

Java erases generic type information during compilation. Therefore, generic type parameters are merely an artifact of the source code and will be absent at runtime.

2.1. Reification

Technically speaking, the generic types are not reified in Java. In programming language's terminology, when a type is present at runtime, we say that type is reified.

The reified types in Java are as follows:

  • Simple primitive types such as long
  • Non-generic abstractions such as String or Runnable
  • Raw types such as List or HashMap
  • Generic types in which all types are unbounded wildcards such as List<?> or HashMap<?, ?>
  • Arrays of other reified types such as String[], int[], List[], or Map<?, ?>[]

Consequently, we can't use something like Map<String, String>.class because the Map<String, String> is not a reified type.

3. Super Type Token

As it turns out, we can take advantage of the power of anonymous inner classes in Java to preserve the type information during compile time:

public abstract class TypeReference<T> {

    private final Type type;

    public TypeReference() {
        Type superclass = getClass().getGenericSuperclass();
        type = ((ParameterizedType) superclass).getActualTypeArguments()[0];
    }

    public Type getType() {
        return type;
    }
}

This class is abstract, so we only can derive subclasses from it.

For example, we can create an anonymous inner:

TypeReference<Map<String, Integer>> token = new TypeReference<Map<String, String>>() {};

The constructor does the following steps to preserve the type information:

  • First, it gets the generic superclass metadata for this particular instance – in this case, the generic superclass is TypeReference<Map<String, Integer>>
  • Then, it gets and stores the actual type parameter for the generic superclass – in this case, it would be Map<String, Integer>

This approach for preserving the generic type information is usually known as super type token:

TypeReference<Map<String, Integer>> token = new TypeReference<Map<String, Integer>>() {};
Type type = token.getType();

assertEquals("java.util.Map<java.lang.String, java.lang.Integer>", type.getTypeName());

Type[] typeArguments = ((ParameterizedType) type).getActualTypeArguments();
assertEquals("java.lang.String", typeArguments[0].getTypeName());
assertEquals("java.lang.Integer", typeArguments[1].getTypeName());

Using super type tokens, we know that the container type is Map, and also, its type parameters are String and Integer. 

This pattern is so famous that libraries like Jackson and frameworks like Spring have their own implementations of it. Parsing a JSON object into a Map<String, String> can be accomplished by defining that type with a super type token:

TypeReference<Map<String, String>> token = new TypeReference<Map<String, String>>() {};
Map<String, String> json = objectMapper.readValue(data, token);

4. Conclusion

In this tutorial, we learned how we can use super type tokens to preserve the generic type information at runtime.

As usual, all the examples are available over on GitHub.

Java bottom

I just announced the new Learn Spring course, focused on the fundamentals of Spring 5 and Spring Boot 2:

>> CHECK OUT THE COURSE
2 Comments
Oldest
Newest
Inline Feedbacks
View all comments
Davide Pugliese
5 months ago

Question, is this about what in Scala are called “higher-kinded” types?
 
e.g.
class Something <T[E]>

Loredana Crusoveanu
1 month ago

Hi Davide,

Not exactly. They both use type constructors, but the super type token pattern uses it to retain the generic type information, while the Scala higher-kinded types are useful for defining modules that take a type as a parameter, which in turn takes another type.

Comments are closed on this article!