Java 8中的模糊方法,为什么会出现这种情况?

19
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下编译成功!

将方法bar修改为:

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

这段代码编译没有问题,但在IntelliJ Idea中报错(Cannot resolve method 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)

如果我说我刚刚成功地使用jdk1.8.0_40运行了你的代码,这会有所帮助吗?我将其粘贴到一个新的Idea项目中,并导入了junit注释,然后点击了“运行”。第一个显然使用Float的-version在任何Java版本上都是错误的,正如你看到的错误一样。 - marvin82
2
@marvin82:在尝试Java8中它失败了。 - Willem Van Onsem
marvin82,在Java8下使用Float编译正常,没有错误。https://gist.github.com/mariuszs/0a82ca08f55499906ea9 - MariuszS
2
我的错,我已经覆盖了语言级别。对于造成的困惑,非常抱歉。 - marvin82
1个回答

21
考虑以下类:
public class Foo extends Exception implements CharSequence {
    //...
}

Foo同时实现了ThrowableCharSequence。因此,如果将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中运行此代码,则没有问题(它会打印字符),因为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

它对异常进行了回退,但找不到可以处理它的类型。
如果在Java8中运行原始代码,则会因为模棱两可的调用而出错;但是,如果在Java7中运行,则将使用Throwable方法。
简而言之:编译器在Java8中旨在“猜测”E是什么类型,而在Java7中选择了最保守的类型。

1
那么,在这种情况下,我们应该避免将接口与类混合使用吗? - MariuszS
2
@MariuszS:嗯,你可以明确指出(例如使用(Exception) bar())你正在谈论哪个E。但是一般来说,将(非finalclassinterface混合使用,或者将interfaceinterface混合使用,可能会引起麻烦。 - Willem Van Onsem
4
通常情况下,应该避免声明像 E bar() 这样的方法,因为这基本上是在告诉调用者“调用者可以决定 bar() 返回什么”。由于唯一有效的返回值是 null,所以这样的方法没有意义。 - Holger
2
@CommuSoft:但是当方法不明确时,C#如何知道要实例化哪种类型?在Java 8中,您可以通过向bar()添加Supplier<E>参数来完成此操作,然后调用者可以使用then(bar(DesiredType::new))通过同时提供适当的Supplier来消除歧义... - Holger
@WillemVanOnsem能否解释一下为什么在Java 8中,<E extends Float> E bar()这种情况可以工作?Float是一个final类,因此永远不可能被子类化。因此,在这种情况下,像Foo这样的类(继承了Float并实现了CharSequence)是如何存在的呢? - Priyal
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接