Выведенные подстановочные дженерики в обратном типе
Java часто может вызывать обобщения на основе аргументов (и даже типа возврата, в отличие от, например, С#).
Пример: у меня есть общий класс Pair<T1, T2>
, который просто хранит пару значений и может использоваться следующим образом:
Pair<String, String> pair = Pair.of("Hello", "World");
Метод of
выглядит так:
public static <T1, T2> Pair<T1, T2> of(T1 first, T2 second) {
return new Pair<T1, T2>(first, second);
}
Очень приятно. Однако это больше не работает для следующего прецедента, для которого требуются подстановочные знаки:
Pair<Class<?>, String> pair = Pair.of((Class<?>) List.class, "hello");
(Обратите внимание на явное приведение, чтобы сделать List.class
правильный тип.)
Код не работает со следующей ошибкой (предоставленной Eclipse):
Несоответствие типов: невозможно преобразовать из TestClass.Pair<Class<capture#1-of ?>,String>
в TestClass.Pair<Class<?>,String>
Однако явно вызов конструктора по-прежнему работает так, как ожидалось:
Pair<Class<?>, String> pair =
new Pair<Class<?>, String>((Class<?>) List.class, "hello");
Может кто-нибудь объяснить это поведение? Это по дизайну? Это нужно? Я делаю что-то неправильно или я наткнулся на недостаток в дизайне/ошибке в компиляторе?
Дикая догадка: "захват # 1-из?" как-то кажется, подразумевает, что подстановочный знак заполняется компилятором "на лету", создавая тип a Class<List>
и, таким образом, не выполняет преобразование (от Pair<Class<?>, String>
до Pair<Class<List>, String>
). Это правильно? Есть ли способ обойти это?
Для полноты использования здесь приведен упрощенный вариант класса Pair
:
public final class Pair<T1, T2> {
public final T1 first;
public final T2 second;
public Pair(T1 first, T2 second) {
this.first = first;
this.second = second;
}
public static <T1, T2> Pair<T1, T2> of(T1 first, T2 second) {
return new Pair<T1, T2>(first, second);
}
}
Ответы
Ответ 1
Причина, по которой работает конструктор, заключается в том, что вы явно указываете параметры типа. Статический метод также будет работать, если вы это сделаете:
Pair<Class<?>, String> pair = Pair.<Class<?>, String>of(List.class, "hello");
Конечно, вся причина, по которой у вас есть статический метод в первую очередь, вероятно, просто для получения вывода типа (который вообще не работает с конструкторами).
Проблема здесь (как вы предположили) заключается в том, что компилятор выполняет преобразование захвата. Я считаю, что это происходит в результате [§15.12.2.6 из JLS]:
- Тип результата выбранного метода определяется следующим образом:
- Если вызываемый метод объявляется с типом возвращаемого значения void, то результат недействителен.
- В противном случае, если необработанное преобразование было необходимо для метод, который будет применяться, тогда тип результата - стирание (п. 4.6) метод объявляет тип возвращаемого значения.
- В противном случае, если вызываемый метод является общим, то для 1in пусть let Fi - формальные параметры типа метод, пусть Ai - фактический тип аргументы, указанные для метода вызов, а R - объявленный возвращаемый тип метода вызывается. Получен тип результата путем применения преобразования захвата (П. 5.1.10) к R [F1: = A1,..., Fn: = ].
- В противном случае тип результата получается путем применения захвата преобразование (§5.1.10) к типу в объявлении метода.
Если вы действительно хотите сделать вывод, одним из возможных способов решения является сделать что-то вроде этого:
Pair<? extends Class<?>, String> pair = Pair.of(List.class, "hello");
Переменная pair
будет иметь более широкий тип, и это означает немного более типизированное имя типа переменной, но по крайней мере вам больше не нужно выполнять вызов метода.