«1. Обзор

В этом руководстве мы познакомимся с токенами супертипа и посмотрим, как они могут помочь нам сохранить информацию об универсальном типе во время выполнения.

2. Стирание

Иногда нам нужно передать определенную информацию о типе методу. Например, здесь мы ожидаем от Джексона преобразования массива байтов JSON в строку:

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

Мы передаем это ожидание через токен буквального класса, в данном случае String.class.

Однако мы не можем так же легко установить такое же ожидание для универсальных типов:

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

Java стирает информацию об универсальных типах во время компиляции. Следовательно, параметры универсального типа являются просто артефактом исходного кода и будут отсутствовать во время выполнения.

2.1. Овеществление

С технической точки зрения, универсальные типы в Java не овеществлены. В терминологии языков программирования, когда тип присутствует во время выполнения, мы говорим, что этот тип овеществлен.

Овеществленные типы в Java следующие:

    Простые примитивные типы, такие как long Неуниверсальные абстракции, такие как String или Runnable Raw типы, такие как List или HashMap Универсальные типы, в которых все типы являются неограниченными подстановочными знаками, такие как List\u003c ?\u003e или HashMap\u003c?, ?\u003e Массивы других материализованных типов, таких как String[], int[], List[] или Map\u003c?, ?\u003e[]

Следовательно, мы не можем использовать что-то вроде Map \u003cString, String\u003e.class, поскольку Map\u003cString, String\u003e не является материализованным типом.

3. Токен супертипа

Как оказалось, мы можем воспользоваться преимуществами анонимных внутренних классов в Java, чтобы сохранить информацию о типе во время компиляции:

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;
    }
}

Этот класс является абстрактным, поэтому мы можем только вывести подклассы из него.

Например, мы можем создать анонимный внутренний код:

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

Конструктор выполняет следующие шаги, чтобы сохранить информацию о типе:

    Сначала он получает общие метаданные суперкласса для этого конкретного экземпляра — в В этом случае универсальный суперкласс — это TypeReference\u003cMap\u003cString, Integer\u003e\u003e. Затем он получает и сохраняет фактический параметр типа для универсального суперкласса — в данном случае это будет Map\u003cString, Integer\u003e

Это Подход к сохранению информации об универсальном типе обычно известен как токен супертипа:

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());

Используя токены супертипа, мы знаем, что тип контейнера — Map, а также параметры его типа — String и Integer.

Этот шаблон настолько известен, что такие библиотеки, как Jackson, и такие фреймворки, как Spring, имеют свои собственные реализации. Разобрать объект JSON в Map\u003cString, String\u003e можно, определив этот тип с токеном супертипа:

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

4. Заключение

В этом руководстве мы узнали, как можно использовать токены супертипа чтобы сохранить информацию об универсальном типе во время выполнения.

Как обычно, все примеры доступны на GitHub.