Java类型推断:引用在Java 8中是模棱两可的,但不是Java 7

假设我们有2个类。一个空类,以及该类的子类。BaseDerived

public class Base {}

public class Derived extends Base {}

然后我们在另一个类中有几个方法:

import java.util.Collection

public class Consumer {

    public void test() {
        set(new Derived(), new Consumer().get());
    }

    public <T extends Base> T get() {
        return (T) new Derived();
    }

    public void set(Base i, Derived b) {
        System.out.println("base");
    }

    public void set(Derived d, Collection<? extends Consumer> o) {
        System.out.println("object");
    }

}

这在 Java 7 中编译和运行成功,但在 Java 8 中无法编译。错误:

Error:(8, 9) java: reference to set is ambiguous
  both method set(Base,Derived) in Consumer and 
  method set(Derived,java.util.Collection) in Consumer match

为什么在 Java 7 中工作,但在 Java 8 中不起作用?<T 如何扩展 Base> Collection 匹配?


答案 1

问题在于类型推断已得到改进。你有一个像这样的方法

public <T extends Base> T get() {
    return (T) new Derived();
}

这基本上是说,“调用方可以决定我返回哪个子类”,这显然是无稽之谈。每个编译器都应该在此处给出有关类型转换的未经检查的警告。Base(T)

现在你有一个方法调用:

set(new Derived(), new Consumer().get());

回想一下,您的方法说“调用方可以决定我返回的内容”。因此,假设可能存在一个同时扩展和实现的类型是完全正确的。所以编译器说“我不知道是调用还是”。Consumer.get()BaseCollectionset(Base i, Derived b)set(Derived d, Collection<? extends Consumer> o)

您可以通过调用来“修复”它,但为了说明方法的疯狂,请注意,您也可以将其更改为set(new Derived(), new Consumer().<Derived>get());

public <X extends Base&Collection<Consumer>> void test() {
    set(new Derived(), new Consumer().<X>get());
}

现在调用时不会有任何编译器警告。实际的不安全操作发生在方法内部。set(Derived d, Collection<? extends Consumer> o)get

因此,正确的解决方法是从方法中删除type参数,并声明它真正返回的内容。getDerived


顺便说一句,让我恼火的是你声称这段代码可以在Java 7下编译。它对嵌套方法调用的有限类型推断导致在嵌套调用上下文中处理该方法,就像返回一样,不能传递给期望的方法。因此,尝试使用符合 Java 7 编译器编译此代码也会失败,但原因不同。getBaseDerived


答案 2

好吧,这并不是说Java7很乐意运行它。在给出错误之前,它会给出几个警告:

jatin@jatin-~$ javac -Xlint:unchecked -source 1.7 com/company/Main.java 
warning: [options] bootstrap class path not set in conjunction with -source 1.7
com/company/Main.java:19: error: no suitable method found for set(Derived,Base)
        set(new Derived(), new Consumer().get());
        ^
    method Consumer.set(Base,Derived) is not applicable
      (argument mismatch; Base cannot be converted to Derived)
    method Consumer.set(Derived,Collection<? extends Consumer>) is not applicable
      (argument mismatch; Base cannot be converted to Collection<? extends Consumer>)

com/company/Main.java:28: warning: [unchecked] unchecked cast
        return (T) new Derived();
                   ^
  required: T
  found:    Derived
  where T is a type-variable:
    T extends Base declared in method <T>get()

问题是这样的:

set(new Derived(), new Consumer().get());

当我们这样做时,如果我们看一下 的签名。它返回我们一个 类型 。我们知道,这以某种方式扩展了.但我们不知道具体是什么。它可以是任何东西。因此,如果我们不能具体决定什么是,那么编译器如何呢?new Consumer().get()getTTBaseTT

告诉编译器的一种方法是通过硬编码并具体通过以下方式告诉它:。set(new Derived(), new Consumer().<Derived>get());

上面的原因是极其极端(故意重复)危险的,是当你尝试这样做时:

class NewDerived extends Base {
     public String getName(){return "name";};
}
NewDerived d = new Consumer().<NewDerived>get();
System.out.println(d.getName());

在Java7(或任何Java版本)中,它将在运行时引发异常:

线程“main” java.lang.ClassCastException 中的异常:com.company.Derived 不能强制转换为 com.company.NewDerived

因为返回一个类型的对象,但您已向编译器提及它是 .而且它不能正确地将 Derived 转换为 NewDerived。这就是它显示警告的原因。getDerivedNewDerived


根据错误,现在我们了解了 出了什么问题。它的类型是 .执行 查找将参数作为 的方法。new Consumer().get()something that extends baseset(new Derived(), new Consumer().get());Derived (or any super class of it), something that extends Base

现在,您的两种方法都符合第一个参数的条件。根据第二个参数,两者都有资格,因为某些东西可以被派生或扩展派生或扩展集合。这就是 Java8 抛出 Error 的原因。something that extends base

根据Java7,它的类型推断有点弱。所以它尝试做类似的事情,

Base base = new Consumer().get();
set(new Derived(), base);

同样,它找不到作为论据的正确方法。因此,它抛出错误,但出于不同的原因:Derived, Base

    set(new Derived(), new Consumer().get());
    ^
method Consumer.set(Base,Derived) is not applicable
  (argument mismatch; Base cannot be converted to Derived)
method Consumer.set(Derived,Collection<? extends Consumer>) is not applicabl e
  (argument mismatch; Base cannot be converted to Collection<? extends Consu mer>)

PS:感谢Holger指出我的答案不完整。


推荐