Подтвердить что ты не робот

Выведенные подстановочные дженерики в обратном типе

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);
    }
}
4b9b3361

Ответ 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 будет иметь более широкий тип, и это означает немного более типизированное имя типа переменной, но по крайней мере вам больше не нужно выполнять вызов метода.