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
- Необработанные типы, такие как
List
илиHashMap
- Универсальные типы, в которых все типы являются неограниченными подстановочными знаками, например
List<?>
илиHashMap<?, ?>
- Массивы других материализованных типов, таких как
String[], int[], List[]
илиMap<?, ?>[]
Следовательно, мы не можем использовать что-то вроде Map<String, String>.class,
потому что Map<String, String>
не является материализованным типом.
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<Map<String, Integer>>.
- Затем он получает и сохраняет фактический параметр типа для универсального суперкласса — в данном случае это будет
Map<String, Integer>.
Этот подход к сохранению информации об универсальном типе обычно известен как токен супертипа :
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<String, String>
можно, определив этот тип с токеном супертипа:
TypeReference<Map<String, String>> token = new TypeReference<Map<String, String>>() {};
Map<String, String> json = objectMapper.readValue(data, token);
4. Вывод
В этом руководстве мы узнали, как использовать токены супертипа для сохранения информации об универсальном типе во время выполнения.
Как обычно, все примеры доступны на GitHub .