Когда вы проектируете API-метод библиотеки, иногда логика его реализации может зависеть от указанного клиентом типа. Особенно часто с этой задачей встречаются при разработке парсеров. Например, библиотека Jackson превращает JSON в объект заданного класса. На интервью этот вопрос можно встретить в виде практической задачи.
Первое, что приходит в голову для решения – дженерик-параметр. Такой подход не сработает, потому что тип будет
стёрт во время компиляции, а логика будет происходить позже, во время выполнения.
Решение, которое сработает для многих случаев – объявление в методе аргумента типа
Class<T>. Пользователь будет передавать в него значение
Foo.class или
fooInstance.getClass(). Проблемы с ним начинаются, когда становится нужно передать generic-тип. Синтаксис
.class не поддерживает дженерики, а
.getClass() от экземпляров
List<String> и
List<Integer> вернет один и тот же объект-описание
сырого типа List.
На помощь приходит техника, описанная в
предыдущей публикации.
1. Объявляется generic класс-обертка над типом:
TypeInformation<T>;. Наш метод будет принимать информацию о типе в виде экземпляра этой обертки.
2. В обертку добавляется конструктор с видимостью protected. Теперь можно создавать объекты только наследников, но не самого этого типа.
3. Пользователь будет передавать экземпляр
анонимного наследника обертки:
new TypeInformation<List<String>>() {}.
4. Внутри вызов
getClass().getGenericSuperclass() вернет
ParameterizedType. Это будет описание типа родителя анонима, то есть самой обертки. Из него с помощью
getActualTypeArguments() можно достать рантайм-информацию о значении дженерика (о
List<String>).