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

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

Следующий класс определяет два метода, оба из которых интуитивно имеют одинаковую функциональность. Каждая функция вызывается с двумя списками типа 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 и интуитивное объяснение того, что может пойти не так, если это не было предотвращено.

4b9b3361

Ответ 1

Эти ответы относятся к 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>, и поэтому компилятор не разрешает ее.

Ответ 2

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

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

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

...

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

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

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

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

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

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

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.