Почему тернарному оператору не нравятся универсальные типы с ограниченными подстановочными знаками?

Следующий класс определяет два метода, оба из которых интуитивно имеют одинаковую функциональность. Каждая функция вызывается с двумя списками типа List<? super Integer> и логическим значением, указывающим, какой из этих списков должен быть назначен локальной переменной.

import java.util.List;

class Example {
    void chooseList1(boolean choice, List<? super Integer> list1, List<? super Integer> list2) {
        List<? super Integer> list;

        if (choice)
            list = list1;
        else
            list = list2;
    }

    void chooseList2(boolean choice, List<? super Integer> list1, List<? super Integer> list2) {
        List<? super Integer> list = choice ? list1 : list2;
    }
}

Согласно javac 1.7.0_45, chooseList1 допустимо, а chooseList2 нет. Он жалуется:

java: incompatible types
  required: java.util.List<? super java.lang.Integer>
  found:    java.util.List<capture#1 of ? extends java.lang.Object>

Я знаю, что правила нахождения типа выражения, содержащего тернарный оператор (… ? … : …), довольно сложны, но, насколько я их понимаю, он выбирает наиболее конкретный тип, в который могут быть преобразованы и второй, и третий аргументы без явный актерский состав. Здесь это должно быть List<? super Integer> list1, но это не так.

Я хотел бы увидеть объяснение, почему это не так, желательно со ссылкой на Спецификацию языка Java и интуитивно понятное объяснение того, что может пойти не так, если это не предотвратить.


person Feuermurmel    schedule 11.01.2014    source источник
comment
@BrianRoach Я не думаю, что это точная копия, хотя и тесно связана. Это относится к условному оператору применительно к типам, выводимым из вызовов универсальных методов, где это применяется к универсальным типам с захватом подстановочных знаков.   -  person Paul Bellora    schedule 12.01.2014


Ответы (2)


Эти ответы относятся к Java 7.

В спецификации языка Java говорится следующее о условный оператор (? :)

В противном случае второй и третий операнды имеют типы S1 и S2 соответственно. Пусть T1 будет типом, полученным в результате применения преобразования упаковки к S1, и пусть T2 будет типом, полученным в результате применения преобразования упаковки в S2.

Тип условного выражения является результатом применения преобразования захвата (§5.1.10) к lub(T1, T2) (§15.12.2.7).

В выражении

List<? super Integer> list = choice ? list1 : list2;

T1 это List<capture#1? super Integer>, а T2 это List<capture#2? super Integer>. Оба они имеют нижнюю границу.

Эта статья подробно описывает о том, как рассчитать lub(T1, T2) (или join function). Возьмем пример оттуда

<T> T pick(T a, T b) {
    return null;
}

<C, A extends C, B extends C> C test(A a, B b) {
    return pick(a, b); // inferred type: Object
}

void tryIt(List<? super Integer> list1, List<? super Integer> list2) {
    test(list1,  list2);
}

Если вы используете IDE и наведете курсор на test(list1, list2), вы заметите, что возвращаемый тип

List<? extends Object>

Это лучшее, что может сделать вывод типа в Java. если list1 было List<Object>, а list2 было List<Number>, единственным допустимым типом возвращаемого значения является List<? extends Object>. Поскольку этот случай должен быть охвачен, метод всегда должен возвращать этот тип.

Точно так же в

List<? super Integer> list = choice ? list1 : list2;

lub(T1, T2) снова равно List<? extends Object>, а преобразование захвата равно List<capture#XX of ? extends Object>.

Наконец, ссылку типа List<capture#XX of ? extends Object> нельзя присвоить переменной типа List<? super Integer>, поэтому компилятор этого не допускает.

person Sotirios Delimanolis    schedule 11.01.2014
comment
Я пока не понял, как вычисляется lub() двух типов. Но почему List<? extends Object> лучшее, что может сделать вывод типа в Java? Было бы полезнее, если бы результат был List<? super Integer>, но какие были бы сложности, т.е. в каком случае это нарушило бы безопасность типов или сделало бы что-то еще невозможным? - person Feuermurmel; 12.01.2014
comment
@Feuermurmel Объявляя ? super Integer, вы объявляете нижнюю границу универсального типа. Это означает, что фактический список может быть любым из List<Integer>, List<Number>, List<Object>. Тип выражения ? : определяется наименьшей верхней границей двух аргументов. Они оба List<? super Integer>, но из-за нижней границы один может быть List<Number>, а другой может быть List<Object>. Java не может определить это во время компиляции, поэтому результатом должен быть некоторый тип, являющийся подтипом Object, т.е. List<? extends Object>. - person Sotirios Delimanolis; 13.01.2014

Время идет, и Java меняется. Я рад сообщить вам, что начиная с Java 8, вероятно, из-за введения целевой ввод, пример Feuermurmels компилируется без проблем.

Текущая версия актуальна. раздел JLS говорит:

Поскольку ссылочные условные выражения могут быть составными выражениями, они могут передавать контекст своим операндам.

...

Это также позволяет использовать дополнительную информацию для улучшения проверки типов вызовов универсальных методов. До Java SE 8 это назначение было хорошо типизировано:

List<String> ls = Arrays.asList();

а этого не было:

List<String> ls = ... ? Arrays.asList() : Arrays.asList("a","b");

Приведенные выше правила позволяют считать оба присваивания хорошо типизированными.

Также интересно отметить, что следующее, полученное из кода Сотириоса Делиманолиса, не компилируется:

void tryIt(List<? super Integer> list1, List<? super Integer> list2) {
    List<? super Integer> l1 = list1 == list2 ? list1 : list2; //  Works fine
    List<? super Integer> l2 = test(list1,  list2); // Error: Type mismatch
}

Это говорит о том, что информация, доступная при вычислении нижней границы типа возвращаемого типа test, отличается от информации о типе условного оператора. Почему это так, я понятия не имею, это может быть интересным вопросом сам по себе.

Я использую jdk_1.8.0_25.

person Lii    schedule 04.04.2015