Java 8中的模糊方法,为什么?

Mar*_*szS 16 java generics ambiguous java-7 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) { }
Run Code Online (Sandbox Code Playgroud)

编译结果(来自命令行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
Run Code Online (Sandbox Code Playgroud)

为什么这种方法含糊不清?这段代码在Java 7下成功编译!

将方法栏更改为:

public static <E extends Float> E bar() {
    return null;
}
Run Code Online (Sandbox Code Playgroud)

这编译没有任何问题,但在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
Run Code Online (Sandbox Code Playgroud)

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)
Run Code Online (Sandbox Code Playgroud)

Wil*_*sem 18

考虑以下课程:

public class Foo extends Exception implements CharSequence {
    //...
}
Run Code Online (Sandbox Code Playgroud)

该类Foo实现了ThrowableCharSequence.因此,如果E设置为此实例,Java编译器不知道要调用哪个方法.

Java7可能没有问题的原因是泛型实现较少.如果你不提供E自己(例如(Foo) bar()),Java将依傍的基本优化版本的Eimplements 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");
    }
}
Run Code Online (Sandbox Code Playgroud)

如果你在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
Run Code Online (Sandbox Code Playgroud)

它做了一个后备,Exception并找不到可以处理它的类型.

如果在Java8中运行原始代码,由于调用模糊,它将会出错,如果在Java7中运行它,它将使用该Throwable方法.


简而言之:编译器旨在"猜测" EJava8中的内容,而在Java7中,选择了最保守的类型.

  • @MariuszS:不,一般来说,你应该避免声明类似`E bar()`的方法,它基本上说"调用者可以决定什么`bar()`"返回.由于唯一可以保存此promise的有效返回值是"null",因此这些方法毫无意义. (4认同)
  • @MariuszS:你可以明确(例如用`(Exception)bar()`关于你正在谈论哪个`E`.但一般来说,maxing(非`final`)`class`es与`interface`s和`与`接口`s的接口`s寻找麻烦我猜. (2认同)
  • @CommuSoft:但是当方法不明确时,C#如何知道实例化哪种类型?在Java 8中,您可以通过向`bar()`添加`Supplier <E>`参数来执行此操作,然后调用者可以使用`then(bar(DesiredType :: new))`来消除歧义,方法是提供适当的`供应商`同时...... (2认同)