Ответ 1
Это действительно законный тип вывода *.
Мы можем свести это к следующему примеру (Ideone):
interface Foo {
<F extends Foo> F bar();
public static void main(String[] args) {
Foo foo = null;
String baz = foo.bar();
}
}
Компилятору разрешено выводить (несовместимый, действительно) тип пересечения String & Foo
, потому что Foo
- это интерфейс. Для примера в вопросе выведено Integer & IElement
.
Это бессмысленно, потому что преобразование невозможно. Мы не можем делать такие роли:
// won't compile because Integer is final
Integer x = (Integer & IElement) element;
Тип вывода в основном работает с:
- набор переменных вывода для каждого из параметров типа метода.
- набор ограничений, которые должны соответствовать.
- иногда ограничения, которые сводятся к границам.
В конце алгоритма каждая переменная разрешена к типу пересечения на основе связанного набора, и если они действительны, то компиляция вызывает.
Процесс начинается с 8.1.3:
Когда начинается вывод, связанный набор обычно генерируется из списка объявлений параметров типа
P1, ..., Pp
и связанных с ними переменных выводаα1, ..., αp
. Такое связанное множество строится следующим образом. Для каждого l (1 ≤ l ≤ p):
[& hellip;]
В противном случае для каждого типа
T
, ограниченного&
в TypeBound, границаαl <: T[P1:=α1, ..., Pp:=αp]
появляется в наборе [& hellip;].
Итак, это означает, что сначала компилятор начинает с привязки F <: Foo
(что означает, что F
является подтипом Foo
).
Переходя к 18.5.2, возвращается тип возвращаемого типа:
Если вызов является поли-выражением, [& hellip;] пусть
R
является типом возвращаемого значенияm
, пустьT
- целевой тип вызова, а затем:
[& hellip;]
В противном случае формула ограничения
‹R θ → T›
уменьшается и включается в [связанный набор].
Формула ограничения ‹R θ → T›
сводится к другой оценке R θ <: T
, поэтому мы имеем F <: String
.
Позже они будут решены согласно 18.4:
[& hellip;] для каждого
αi
определен экземпляр-кандидатTi
:
- В противном случае, где
αi
имеет собственные верхние границыU1, ..., Uk
,Ti = glb(U1, ..., Uk)
.Оценки
α1 = T1, ..., αn = Tn
включены в текущий связанный набор.
Напомним, что наш набор оценок F <: Foo, F <: String
. glb(String, Foo)
определяется как String & Foo
. Это, по-видимому, законный тип для glb, который требует только:
Это ошибка времени компиляции, если для любых двух классов (не интерфейсов)
Vi
иVj
,Vi
не является подклассомVj
или наоборот.
Наконец:
Если разрешение преуспеть с экземплярами
T1, ..., Tp
для переменных выводаα1, ..., αp
, пустьθ'
- подстановка[P1:=T1, ..., Pp:=Tp]
. Тогда:
- Если для метода не требуется немедленное преобразование, тогда тип вызова
m
получается, применяяθ'
к типуm
.
Таким образом, метод используется с String & Foo
как тип F
. Конечно, мы можем назначить это String
, поэтому невозможно преобразовать a Foo
в String
.
Тот факт, что String
/Integer
являются конечными классами, по-видимому, не рассматривается.
* Примечание: стирание типа/полностью не связано с проблемой.
Кроме того, хотя это компилируется и на Java 7, я считаю разумным сказать, что нам не нужно беспокоиться о спецификации там. Вывод типа Java 7 был по существу менее сложной версией Java 8. Он компилируется по тем же причинам.
В качестве дополнения, хотя это и странно, это, вероятно, никогда не вызовет проблему, которая еще не присутствовала. Редко полезно писать общий метод, возвращаемый тип которого выведен исключительно из возвращаемой цели, так как только null
может быть возвращен из такого метода без кастинга.
Предположим, например, что у нас есть некоторый аналог отображения, в котором хранятся подтипы определенного интерфейса:
interface FooImplMap {
void put(String key, Foo value);
<F extends Foo> F get(String key);
}
class Bar implements Foo {}
class Biz implements Foo {}
Это уже совершенно допустимо для создания такой ошибки, как:
FooImplMap m = ...;
m.put("b", new Bar());
Biz b = m.get("b"); // casting Bar to Biz
Таким образом, тот факт, что мы также можем сделать Integer i = m.get("b");
, не является новой возможностью для ошибки. Если бы мы программировали такой код, это было уже потенциально необоснованным.
Как правило, параметр типа должен быть только выведен из целевого типа, если нет причин для его привязки, например. Collections.emptyList()
и Optional.empty()
:
private static final Optional<?> EMPTY = new Optional<>();
public static<T> Optional<T> empty() {
@SuppressWarnings("unchecked")
Optional<T> t = (Optional<T>) EMPTY;
return t;
}
Это A-OK, потому что Optional.empty()
не может ни производить, ни потреблять T
.