Супер тип жетони в Java Generics

1. Общ преглед

В този урок ще се запознаем със символите от супер тип и ще видим как те могат да ни помогнат да запазим обща информация за типа по време на изпълнение.

2. Изтриването

Понякога трябва да предадем информация за определен тип на метод . Например тук очакваме от Джаксън да преобразува байтовия масив JSON в String:

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

Ние съобщаваме това очакване чрез буквален маркер на класа, в този случай String.class.

Не можем обаче да зададем същото очакване за родови типове толкова лесно:

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

Java изтрива обща информация за типа по време на компилацията. Следователно, параметрите от общ тип са просто артефакт на изходния код и ще липсват по време на изпълнение.

2.1. Одобрение

Технически погледнато, родовите типове не са преидентифицирани в Java. В терминологията на езика за програмиране, когато даден тип присъства по време на изпълнение, ние казваме, че този тип е преифициран.

Преифицираните типове в Java са както следва:

  • Прости примитивни типове като long
  • Негенерични абстракции като String или Runnable
  • Сурови видове като List или HashMap
  • Общи типове, при които всички типове са неограничени заместващи символи като List или HashMap
  • Масиви от други преифицирани типове като String [], int [], List [] или Map []

Следователно не можем да използваме нещо като Map.class, защото картата не е преифициран тип.

3. Супер тип жетон

Както се оказва, можем да се възползваме от силата на анонимните вътрешни класове в Java, за да запазим информацията за типа по време на компилация:

public abstract class TypeReference { private final Type type; public TypeReference() { Type superclass = getClass().getGenericSuperclass(); type = ((ParameterizedType) superclass).getActualTypeArguments()[0]; } public Type getType() { return type; } }

Този клас е абстрактен, така че можем да извлечем само подкласове от него.

Например, можем да създадем анонимен вътрешен:

TypeReference token = new TypeReference() {};

Конструкторът прави следните стъпки, за да запази информацията за типа:

  • Първо, той получава общите метаданни за суперклас за този конкретен екземпляр - в този случай родовият суперклас е TypeReference
  • След това получава и съхранява действителния параметър на типа за родовия суперклас - в този случай това би било Map

Този подход за запазване на общата информация за типа обикновено е известен като маркер супер тип :

TypeReference token = new TypeReference() {}; Type type = token.getType(); assertEquals("java.util.Map", type.getTypeName()); Type[] typeArguments = ((ParameterizedType) type).getActualTypeArguments(); assertEquals("java.lang.String", typeArguments[0].getTypeName()); assertEquals("java.lang.Integer", typeArguments[1].getTypeName());

Използвайки маркери от супер тип, знаем, че типът контейнер е Map, а параметрите на типа са String и Integer.

Този модел е толкова известен, че библиотеки като Джаксън и рамки като Spring имат свои собствени реализации. Анализирането на JSON обект в Карта може да се извърши чрез дефиниране на този тип с маркер супер тип:

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

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

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

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