Контекст
Я работаю над проектом, который сильно зависит от универсальных типов. Одним из его ключевых компонентов является так называемый TypeToken
, который предоставляет способ представления универсальных типов во время выполнения и применения к ним некоторых служебных функций. Чтобы избежать Java Type Erasure, я использую нотацию в фигурных скобках ({}
) для создания автоматически сгенерированного подкласса, так как это делает тип пригодным для повторного использования.
Что в основном делает TypeToken
Это сильно упрощенная версия TypeToken
которая TypeToken
чем оригинальная реализация. Тем не менее, я использую этот подход, чтобы убедиться, что настоящая проблема не заключается в одной из этих служебных функций.
public class TypeToken<T> {
private final Type type;
private final Class<T> rawType;
private final int hashCode;
/* ==== Constructor ==== */
@SuppressWarnings("unchecked")
protected TypeToken() {
ParameterizedType paramType = (ParameterizedType) this.getClass().getGenericSuperclass();
this.type = paramType.getActualTypeArguments()[0];
// ...
}
Когда это работает
По сути, эта реализация отлично работает практически в любой ситуации. У него нет проблем с обработкой большинства типов. Следующие примеры работают отлично:
TypeToken<List<String>> token = new TypeToken<List<String>>() {};
TypeToken<List<? extends CharSequence>> token = new TypeToken<List<? extends CharSequence>>() {};
Поскольку он не проверяет типы, реализация, представленная выше, допускает каждый тип, разрешенный компилятором, включая TypeVariables.
<T> void test() {
TypeToken<T[]> token = new TypeToken<T[]>() {};
}
В этом случае type
является GenericArrayType
содержащим TypeVariable
качестве типа компонента. Это прекрасно.
Странная ситуация при использовании лямбд
Однако, когда вы инициализируете TypeToken
внутри лямбда-выражения, все начинает меняться. (Переменная типа взята из test
функции выше)
Supplier<TypeToken<T[]>> sup = () -> new TypeToken<T[]>() {};
В этом случае type
по-прежнему является GenericArrayType
, но в качестве типа компонента он содержит значение null
.
Но если вы создаете анонимный внутренний класс, все снова начинает меняться:
Supplier<TypeToken<T[]>> sup = new Supplier<TypeToken<T[]>>() {
@Override
public TypeToken<T[]> get() {
return new TypeToken<T[]>() {};
}
};
В этом случае тип компонента снова содержит правильное значение (TypeVariable)
Результирующие вопросы
- Что происходит с TypeVariable в лямбда-примере? Почему вывод типа не учитывает универсальный тип?
- В чем разница между явно объявленным и неявно объявленным примером? Является ли вывод типа единственной разницей?
- Как я могу это исправить, не используя явную декларацию? Это становится особенно важным в модульном тестировании, поскольку я хочу проверить, вызывает ли конструктор исключения или нет.
Чтобы пояснить это немного: это не проблема, которая "актуальна" для программы, поскольку я вообще НЕ разрешаю неразрешимые типы, но это все еще интересное явление, которое я хотел бы понять.
Мое исследование
Обновление 1
Тем временем я провел некоторые исследования на эту тему. В Спецификации языка Java §15.12.2.2 я нашел выражение, которое может иметь к этому какое-то отношение - "относящееся к применимости", упомянув "неявно типизированное лямбда-выражение" в качестве исключения. Очевидно, это неверная глава, но выражение используется в других местах, включая главу о выводе типа.
Но, честно говоря: я еще не понял, что все эти операторы любят :=
или Fi0
что делает его действительно трудным для понимания в деталях. Я был бы рад, если бы кто-то мог прояснить это немного, и если это могло бы быть объяснением странного поведения.
Обновление 2
Я снова подумал об этом подходе и пришел к выводу, что даже если компилятор удалит тип, поскольку он "не имеет отношения к применимости", он не имеет смысла устанавливать тип компонента на null
вместо самого щедрого типа Объект. Я не могу придумать ни одной причины, по которой дизайнеры языка решили это сделать.
Обновление 3
Я только что перепроверил тот же код с последней версией Java (я использовал 8u191
раньше). К моему сожалению, это ничего не изменило, хотя вывод типа Java был улучшен...
Обновление 4
Я запросил запись в официальной Java Bug Database/Tracker несколько дней назад, и она только что была принята. Поскольку разработчики, просмотревшие мой отчет, присвоили этой ошибке приоритет P4, может пройти некоторое время, пока она не будет исправлена. Вы можете найти отчет здесь.
Огромный привет Тому Хоутину - тот факт, что он может быть существенной ошибкой в самой Java SE. Тем не менее, отчет Майка Штробеля, вероятно, будет более подробным, чем мой, из-за его впечатляющих знаний. Однако, когда я написал отчет, ответ Стробеля еще не был доступен.