Неоднозначная перегрузка в 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, где это необходимо, но это не такой случай.