Неоднозначная перегрузка в Java8 - это ECJ или javac правильно?
У меня есть следующий класс:
import java.util.HashSet;
import java.util.List;
public class OverloadTest<T> extends HashSet<List<T>> {
private static final long serialVersionUID = 1L;
public OverloadTest(OverloadTest<? extends T> other) {}
public OverloadTest(HashSet<? extends T> source) {}
private OverloadTest<Object> source;
public void notAmbigious() {
OverloadTest<Object> o1 = new OverloadTest<Object>(source);
}
public void ambigious() {
OverloadTest<Object> o2 = new OverloadTest<>(source);
}
}
Это компилируется под JDK 7 javac, а также затмение (с соблюдением соответствия 1.7 или 1.8). Однако, пытаясь скомпилировать под JDK 8 javac, я получаю следующую ошибку:
[ERROR] src/main/java/OverloadTest.java:[18,35] reference to OverloadTest is ambiguous
[ERROR] both constructor <T>OverloadTest(OverloadTest<? extends T>) in OverloadTest and constructor <T>OverloadTest(java.util.HashSet<? extends T>) in OverloadTest match
Обратите внимание, что эта ошибка применяется только к вызову конструктора в методе ambigous()
, а не методе notAmbiguous()
. Единственное отличие состоит в том, что ambiguous()
полагается на оператор алмаза.
Мой вопрос заключается в следующем: Явак под JDK 8 правильно помечен двусмысленным разрешением или был javac под JDK 7, неспособным поймать двусмысленность? В зависимости от ответа мне нужно либо записать ошибку JDK, либо ошибку ecj.
Ответы
Ответ 1
В вызове, когда конструктор вызывается с T явно, нет двусмысленности:
OverloadTest<Object> o1 = new OverloadTest<Object>(source);
Поскольку T определяется во время вызова конструктора, объект передает? extends Проверка объекта во время компиляции просто прекрасна, и проблем нет. Когда T явно задано для Object, выбор для двух конструкторов будет следующим:
public OverloadTest(OverloadTest<Object> other) {}
public OverloadTest(HashSet<Object> source) {}
И в этом случае компилятор очень легко выбрать первый. В другом примере (с использованием оператора алмаза) T явно не задано, поэтому компилятор сначала пытается определить T, проверяя тип фактического параметра, который первый вариант не нужно делать.
Если второй конструктор был изменен, чтобы правильно отразить то, что, я думаю, является желаемой операцией (что, поскольку OverloadTest - это HashSet из списков T, то передача в HashSet из списков T должна быть такой):
public OverloadTest(HashSet<List<? extends T>> source) {}
... тогда двусмысленность разрешается. Но по мере того как он в настоящее время стоит, будет конфликт, когда вы попросите компилятор разрешить этот неоднозначный вызов.
Компилятор увидит оператора алмаза и попытается разрешить T на основе того, что было передано и что ожидали различные конструкторы. Но способ написания конструктора HashSet гарантирует, что независимо от того, какой класс передан, оба конструктора останутся в силе, потому что после стирания T всегда заменяется на Object. И когда T является объектом, конструктор HashSet и конструктор OverloadTest имеют аналогичные стирания, потому что OverloadTest является допустимым экземпляром HashSet. И поскольку один конструктор не переопределяет другой (поскольку OverloadTest <T> не расширяет HashSet <T> ), на самом деле нельзя сказать, что он более конкретный, чем другой, поэтому он не будет знать, как сделать выбор, и вместо этого вывести ошибку компиляции.
Это происходит только потому, что, используя T как границу, вы принудительно выполняете компилятор для проверки типов. Если вы просто сделали это & lt; > вместо <? расширяет T > он будет компилироваться просто отлично. Компилятор Java 8 более строг в отношении типов и стираний, чем Java 7, частично потому, что многие из новых функций Java 8 (например, методы защиты интерфейса) требовали, чтобы они были немного более педантичными в отношении генериков. Java 7 неправильно сообщала об этом.
Ответ 2
Извините, что испортил вечеринку, но перегрузочное разрешение в Java более сложно, чем комментарии и ответ, увиденные до сих пор.
Оба конструктора применимы:
OverloadTest(OverloadTest<? extends T>)
OverloadTest(HashSet<? extends T>)
В этот момент Java 8 использует "Более конкретный метод вывода" , в котором анализируются все пары кандидатов.
Во время проверки, если прежний конструктор более специфичен, чем последний, следующее ограничение успешно разрешено ( T # 0 является переменной вывода для параметра типа T
HashSet
):
OverloadTest<? extends T> <: HashSet<? extends T#0>
Решение состоит в создании экземпляра T # 0 до List<T>
.
Обратная попытка не может разрешить следующее ограничение (здесь T # 0 является переменной вывода для параметра типа T
OverloadTest
):
HashSet<? extends T> <: OverloadTest<? extends T#0>
Не найдено экземпляра для T # 0, чтобы удовлетворить это ограничение.
Поскольку одно направление преуспевает и другое направление выходит из строя, прежний конструктор считается более конкретным, чем последний, тем самым устраняя двусмысленность.
Ergo: принятие программы, кажется, правильный ответ от компилятора.
PS: Я не спорю о более педантичном типе проверки на Java 8, где это необходимо, но это не такой случай.