Нижняя ограниченная дикая карта вызывает проблемы в javac, но не Eclipse
Этот фрагмент кода компилируется в Eclipse, но не в javac:
import java.util.function.Consumer;
public class Test {
public static final void m1(Consumer<?> c) {
m2(c);
}
private static final <T> void m2(Consumer<? super T> c) {
}
}
Выход javac:
C:\Users\lukas\workspace>javac -version
javac 1.8.0_92
C:\Users\lukas\workspace>javac Test.java
Test.java:5: error: method m2 in class Test cannot be applied to given types;
m2(c);
^
required: Consumer<? super T>
found: Consumer<CAP#1>
reason: cannot infer type-variable(s) T
(argument mismatch; Consumer<CAP#1> cannot be converted to Consumer<? super T>)
where T is a type-variable:
T extends Object declared in method <T>m2(Consumer<? super T>)
where CAP#1 is a fresh type-variable:
CAP#1 extends Object from capture of ?
1 error
----------------------------------------------------------------
Какой компилятор ошибочен и почему? (Отчет об ошибках Eclipse и части обсуждения здесь)
Ответы
Ответ 1
Этот код является законным по JLS 8. javac версии 8 и ранее имели несколько ошибок в том, как они обрабатывают подстановочные знаки и захваты. Начиная с версии 9 (ранний доступ, я пробовал версию ea-113 и новее), также javac принимает этот код.
Чтобы понять, как компилятор анализирует это в соответствии с JLS, важно рассказать обо всем, что такое подстановочные знаки, переменные типа, переменные вывода и т.д.
Тип c
равен Consumer<capture#1-of ?>
(javac будет писать Consumer<CAP#1>
). Этот тип неизвестен, но исправлен.
Параметр m2
имеет тип Consumer<? super T>
, где T
- это переменная типа, которая создается экземпляром типа.
Во время ввода типа для представления T
используется переменная вывода, обозначаемая ecj как T#0
.
Вывод типа состоит в определении того, может ли T#0
быть экземпляр любым типом без нарушения каких-либо ограничений типа. В этом конкретном случае мы начнем с этого противопоказания:
⟨c → Consumer <? super T # 0 > ⟩
который ступенчато уменьшается (применяя JLS 18.2):
⟨Конкурс < захват # 1-of? > → Потребитель <? super T # 0 > ⟩
⟨capture # 1-of? <= & alpha; super T # 0⟩
⟨T # 0 <: capture # 1-of?⟩
T # 0 <: capture # 1-of?
Последняя строка - это "привязка типа" и выполняется сокращение. Поскольку никаких дополнительных ограничений не требуется, разрешение тривиально создает T#0
для типа capture#1-of ?
.
К этим шагам вывод типа доказал, что m2
применим для этого конкретного вызова. QED.
Интуитивно показано, что показанное решение говорит нам: какой бы тип захвата не представлялся, если T
установлен для представления одного и того же типа, никаких ограничений типа не нарушаются. Это возможно, поскольку захват фиксируется перед началом вывода типа.
Ответ 2
Обратите внимание, что следующее может быть скомпилировано без проблем:
public class Test {
public static final void m1(Consumer<?> c) {
m2(c);
}
private static final <T> void m2(Consumer<T> c) {
}
}
Несмотря на то, что мы не знаем фактического типа потребителя, мы знаем, что он будет присваиваться Consumer<T>
, хотя мы не знаем, что T
(не знаю, что такое T
, является нормой в общем коде в любом случае).
Но если присвоение Consumer<T>
допустимо, значит, присвоение Consumer<? super T>
будет. Мы можем даже показать это практически с промежуточным шагом:
public class Test {
public static final void m1(Consumer<?> c) {
m2(c);
}
private static final <T> void m2(Consumer<T> c) {
m3(c);
}
private static final <T> void m3(Consumer<? super T> c) {
}
}
Нет объектов компилятора.
Он также будет принят при замене дикой карты на именованный тип, например
public class Test {
public static final void m1(Consumer<?> c) {
m2(c);
}
private static final <E,T extends E> void m2(Consumer<E> c) {
}
}
Здесь E
является супер-типом T
, как и ? super T
.
Я попытался найти отчет об ошибке для javac
, ближайшего к этому сценарию, но когда дело доходит до javac
и подстановочных типов, их так много, что я в конце концов сдался. Отказ от ответственности: это не означает, что существует так много ошибок, просто так много связанных сценариев, которые могут быть разными симптомами одной и той же ошибки.
Единственное, что имеет значение, состоит в том, что он уже исправлен в Java 9.