Ответ 1
Спасибо за отчет. Это похоже на ошибку. Я позабочусь об этом и, вероятно, добавлю лучший ответ, если у нас появится больше информации о том, почему это происходит. Я зарегистрировал эту запись ошибки JDK-8043926, чтобы ее отслеживать.
Эта программа отлично компилируется в Java 7 (или в Java 8 с -source 7
), но не скомпилируется с Java 8:
interface Iface<T> {}
class Impl implements Iface<Impl> {}
class Acceptor<T extends Iface<T>> {
public Acceptor(T obj) {}
}
public class Main {
public static void main(String[] args) {
Acceptor<?> acceptor = new Acceptor<>(new Impl());
}
}
Результат:
Main.java:10: error: incompatible types: cannot infer type arguments for Acceptor<>
Acceptor<?> acceptor = new Acceptor<>(new Impl());
^
reason: inference variable T has incompatible bounds
equality constraints: Impl
upper bounds: Iface<CAP#1>,Iface<T>
where T is a type-variable:
T extends Iface<T> declared in class Acceptor
where CAP#1 is a fresh type-variable:
CAP#1 extends Iface<CAP#1> from capture of ?
1 error
Другими словами, это несовместимость исходного кода между Java 7 и 8. Я прошел через Несовместимость между Java SE 8 и Java SE 7, но не нашел ничего, что бы соответствовало моей проблеме.
Итак, это ошибка?
Окружающая среда:
$ /usr/lib/jvm/java-8-oracle/bin/java -version
java version "1.8.0"
Java(TM) SE Runtime Environment (build 1.8.0-b132)
Java HotSpot(TM) 64-Bit Server VM (build 25.0-b70, mixed mode)
Спасибо за отчет. Это похоже на ошибку. Я позабочусь об этом и, вероятно, добавлю лучший ответ, если у нас появится больше информации о том, почему это происходит. Я зарегистрировал эту запись ошибки JDK-8043926, чтобы ее отслеживать.
Спецификация языка Java значительно изменилась в отношении вывода типа. В JLS7 вывод типа был описан в §15.12.2.7 и §15.12.2.8, тогда как в JLS8, есть целая глава, посвященная Глава 18. Типовой вывод.
Правила довольно сложны, как в JLS7, так и в JLS8. Трудно сказать различия, но, очевидно, есть различия, как видно из раздела §18.5.2:
Эта стратегия вывода отличается от Java SE 7 Edition Спецификации языка Java [..].
Однако намерение изменения должно было быть обратно совместимым. См. Последний параграф раздела §18.5.2:
[..] Стратегия допускает разумные результаты в типичных случаях использования и обратно совместима с алгоритмом в Java SE 7 Edition Спецификации языка Java.
Я не могу сказать, правда это или нет. Однако есть некоторые интересные варианты вашего кода, которые не показывают проблему. Например, следующий оператор компилируется без ошибок:
new Acceptor<>(new Impl());
В этом случае нет целевого типа. Это означает, что выражение экземпляра класса не является поли выражением, а правила вывода типа проще. См. §18.5.2:
Если вызов не является поли-выражением, пусть связанный набор B 3 будет таким же, как B 2.
Это также причина, почему работает следующее утверждение.
Acceptor<?> acceptor = (Acceptor<?>) new Acceptor<>(new Impl());
Хотя в контексте выражения есть тип, он не считается типом цели. Если выражение создания экземпляра класса не происходит ни в выражении присваивания, ни в вызове-вызове, то это не может быть политическим выражением. См. §15.9:
Выражение создания экземпляра класса представляет собой выражение poly (§15.2), если оно использует форму алмаза для аргументов типа для класса и появляется в контексте назначения или контексте вызова (§5.2, §5.3). В противном случае это отдельное выражение.
Возвращаясь к вашему заявлению. Соответствующая часть JLS8 снова §18.5.2. Тем не менее, я не могу сказать, правилен ли следующий оператор в соответствии с JLS8, если компилятор прав с сообщением об ошибке. Но, по крайней мере, у вас есть альтернативы и указатели для получения дополнительной информации.
Acceptor<?> acceptor = new Acceptor<>(new Impl());
Ввод типа был изменен в Java 8. Теперь введите вывод как для целевого типа, так и для типов параметров для обоих конструкторов и методов. Рассмотрим следующее:
interface Iface {}
class Impl implements Iface {}
class Impl2 extends Impl {}
class Acceptor<T> {
public Acceptor(T obj) {}
}
<T> T foo(T a) { return a; }
В Java 8 (но не в Java 7) теперь нормально:
Acceptor<Impl> a = new Acceptor<>(new Impl2());
// Java 8 cleverly infers Acceptor<Impl>
// While Java 7 infers Acceptor<Impl2> (causing an error)
Это, конечно, дает ошибку в обоих:
Acceptor<Impl> a = new Acceptor<Impl2>(new Impl2());
Это также нормально в Java 8:
Acceptor<Impl> a = foo (new Acceptor<>(new Impl2()));
// Java 8 infers Acceptor<Impl> even in this case
// While Java 7, again, infers Acceptor<Impl2>
// and gives: incompatible types: Acceptor<Impl2> cannot be converted to Acceptor<Impl>
Ниже приведена ошибка для обоих, но ошибка отличается:
Acceptor<Impl> a = foo (new Acceptor<Impl2>(new Impl2()));
// Java 7:
// incompatible types: Acceptor<Impl2> cannot be converted to Acceptor<Impl>
// Java 8:
// incompatible types: inferred type does not conform to upper bound(s)
// inferred: Acceptor<Impl2>
// upper bound(s): Acceptor<Impl>,java.lang.Object
Очевидно, что Java 8 сделала систему вывода типов более умной. Означает ли это несовместимость? Как правило, нет. Из-за стирания типа на самом деле не имеет значения, какие типы были выведены, пока компиляция программы. Содержит ли Java 8 все программы на Java 7? Он должен, но вы подняли случай, когда он этого не делает.
Что, кажется, происходит, так это то, что Java 8 отлично не работает с подстановочными знаками. Вместо того, чтобы рассматривать их как недостаток ограничения, кажется, что они рассматривают их как ограничительное ограничение, которое оно не может удовлетворить. Я не уверен, что это следует за буквой JLS, но я бы назвал это ошибкой хотя бы по духу.
FYI, это работает (обратите внимание, что у моего Acceptor
нет ограничений типа, которые у вас есть):
Acceptor<?> a = new Acceptor<>(new Impl2());
Обратите внимание, что ваш пример использует подстановочный шаблон вне параметра метода (что нецелесообразно), интересно, будет ли та же проблема в более типичном коде, который использует оператор алмаза при вызове метода. (Возможно.)