Неоднозначный метод в 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)

person MariuszS    schedule 07.04.2015    source источник
comment
Поможет ли это, если я скажу, что только что успешно запустил ваш код с jdk1.8.0_40? Просто вставил его в новый проект Idea и нажал «Выполнить» с импортированной аннотацией junit. Первая, очевидно, -версия с Float определенно неверна для любой версии Java, как видно из ошибки.   -  person marvin82    schedule 07.04.2015
comment
@marvin82: в попробовать Java8 это не удается.   -  person Willem Van Onsem    schedule 07.04.2015
comment
marvin82, он компилируется нормально с Float под java8, ошибки нет gist.github .com/mariuszs/0a82ca08f55499906ea9   -  person MariuszS    schedule 07.04.2015
comment
Плохо, у меня переопределен языковой уровень. Извините за недопонимание.   -  person marvin82    schedule 07.04.2015


Ответы (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 был выбран наиболее консервативный тип.

person Willem Van Onsem    schedule 07.04.2015
comment
Итак, в этом случае мы должны избегать смешивания интерфейсов с классами? - person MariuszS; 07.04.2015
comment
@MariuszS: ну, вы можете сделать явным (например, с (Exception) bar(), о котором E вы говорите. Но в целом максимизация (не final) classes с interfaces и interfaces с interfaces, я думаю, ищет проблемы. - person Willem Van Onsem; 07.04.2015
comment
@MariuszS: Нет, как правило, вам следует избегать объявления таких методов, как E bar(), которые в основном говорят: «вызывающий может решить, что возвращает bar()». Поскольку единственным допустимым возвращаемым значением, которое может содержать это обещание, является null, такие методы не имеют смысла. - person Holger; 08.04.2015
comment
@Holger: ну, в C# вы также можете добавить ограничение new(). В этом случае вы принудительно должны иметь доступное ограничение по умолчанию. В этом случае он становится полезным. Насколько я знаю, это недоступно в Java8, но в конечном итоге это может быть реализовано. - person Willem Van Onsem; 08.04.2015
comment
@CommuSoft: но когда метод неоднозначен, как C # узнает, какой тип создавать? В Java 8 вы можете сделать это, добавив параметр Supplier<E> к bar(), тогда вызывающая сторона может использовать then(bar(DesiredType::new)) для устранения неоднозначности, одновременно предоставив соответствующий Supplier - person Holger; 08.04.2015
comment
@Holger: потому что в C# вы вынуждены указывать тип явно, если только это не тип this (методы расширения). Я только говорю, что в (ближайшем) будущем такие E могут стать интересными (по причинам, которые мы пока не знаем). Не поймите меня неправильно, поставил +1 к вашему комментарию, в настоящее время это не очень хорошая идея, но я думаю, что с такими утверждениями нужно быть осторожным. - person Willem Van Onsem; 08.04.2015
comment
@WillemVanOnsem, можете ли вы объяснить, почему в Java 8 это сработало для сценария <E extends Float> E bar()? Плавающий, являющийся конечным классом, никогда не может быть подклассом. Поэтому в таком случае как существует класс Like Foo (расширяет Float и реализует CharSequence )? - person Priyal85; 13.10.2017