Неоднозначный метод в Java 8, почему?

public static void main(String... args){
    then(bar()); // Compilation Error
}

public static <E extends Exception> E bar() {
    return null;
}

public static void then(Throwable actual) { }

public static void then(CharSequence actual) { }

Результат компиляции (из командной строки javac Ambiguous.java)

Ambiguous.java:4: error: reference to then is ambiguous
        then(bar());
        ^
  both method then(Throwable) in Ambiguous and method then(CharSequence) in Ambiguous match
1 error

Почему этот метод неоднозначен? Этот код успешно скомпилирован в Java 7!

После изменения панели методов:

public static <E extends Float> E bar() {
    return null;
}

Это компилируется без каких-либо проблем, но сообщается как ошибка в IntelliJ Idea (не удается разрешить метод then(java.lang.FLoat)).

Этот код выходит из строя под Java 7 - javac -source 1.7 Ambiguous.java:

Ambiguous.java:4: error: no suitable method found for then(Float)
        then(bar());
        ^
    method Ambiguous.then(Throwable) is not applicable
      (argument mismatch; Float cannot be converted to Throwable)
    method Ambiguous.then(CharSequence) is not applicable
      (argument mismatch; Float cannot be converted to CharSequence)
1 error

Версия Java

java version "1.8.0_40"
Java(TM) SE Runtime Environment (build 1.8.0_40-b25)
Java HotSpot(TM) 64-Bit Server VM (build 25.40-b25, mixed mode)

Ответы

Ответ 1

Рассмотрим следующий класс:

public class Foo extends Exception implements CharSequence {
    //...
}

Класс Foo реализует как Throwable, так и CharSequence. Поэтому в случае, если для этого экземпляра установлен E, компилятор Java не знает, какой метод вызывать.

Причина, по которой, вероятно, нет проблем для Java7, заключается в том, что генерические средства менее реализованы. Если вы не предоставите E самостоятельно (например, (Foo) bar()), Java вернется к базовому варианту E, который равен implements Exception, E, таким образом, считается только экземпляром Exception.

В Java8 улучшен вывод типа , тип E теперь получен из параметр, вызываемый then(), другими словами, компилятор сначала рассматривает возможные типы then(), проблема в том, что они оба являются допустимыми выборами. Поэтому в этом случае он становится неоднозначным.


Доказательство концепции:

Теперь мы немного изменим ваш код и покажем, как разрешены неоднозначные вызовы:

Скажем, мы модифицируем код:

public class Main {
    public static void main(String... args){
        then(bar()); // Compilation Error
    }
    public static <E extends Exception> E bar() {
        return null;
    }
    public static void then(CharSequence actual) {
        System.out.println("char");
    }
}

Если вы запустите это в Java8, нет проблем (он печатает char), потому что Java8 просто предполагает, что существует такой класс Foo (он создал какой-то "внутренний" тип для нее, которая получена из обоих).

Запуск этого в Java7 дает проблемы:

/MyClass.java:18: error: method then in class MyClass cannot be applied to given types;
    then(bar()); // Compilation Error
    ^
  required: CharSequence
  found: Exception
  reason: actual argument Exception cannot be converted to CharSequence by method invocation conversion
1 error

Он сделал резервную копию Exception и не смог найти тип, который мог бы с ним справиться.

Если вы запустили исходный код в Java8, это приведет к ошибке из-за неоднозначного вызова, если вы запустите его в Java7, однако он будет использовать Throwable метод.


Вкратце: компилятор стремится "угадать", что E в Java8, тогда как в Java7 был выбран наиболее консервативный тип.