Javatypes推断:在Java 8中引用是不明确的,但不是Java 7

可以说我们有两个class。 一个空的Base类和这个Derived类的一个子类。

 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 extends Base>如何匹配集合?

问题在于types推断得到了改进 。 你有一个方法

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

基本上说,“调用者可以决定Base I的哪个子类返回”,这显然是无稽之谈。 每个编译器都应该给你一个关于你的typescast (T)的未经检查的警告。

现在你有一个方法调用:

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

回想一下,你的方法Consumer.get()说“调用者可以决定我返回”。 所以假设可能有一种扩展Base并同时实现Collection的types是完全正确的。 所以编译器说:“我不知道是否调用set(Base i, Derived b)set(Derived d, Collection<? extends Consumer> o) ”。

你可以通过调用set(new Derived(), new Consumer().<Derived>get());来修复它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方法内部。

所以正确的解决方法是从get方法中删除types参数,并声明它真正返回的Derived


顺便提一句,你们声称这个代码可以在Java 7下编译,它的嵌套方法调用的有限types推理导致在嵌套的调用上下文中处理get方法,比如返回不能传递给的Base期望Derived的方法。 因此,试图使用符合Java 7编译器编译此代码也会失败,但出于不同的原因。

那么,它不像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() ,如果我们看get签名。 它返回给我们一个T 。 我们知道T以某种方式扩展了Base 。 但是我们不知道T是什么。 它可以是任何东西。 所以如果我们不能明确地决定T是什么,那么编译器怎么样?

一种告诉编译器的方式是通过硬编码和具体的告诉它: 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版本)中,它将在运行时抛出exception:

线程“main”中的exceptionjava.lang.ClassCastException:com.company.Derived不能转换为com.company.NewDerived

因为get返回Derivedtypes的对象,但是你已经提到编译器它是NewDerived 。 它不能正确地将Derived转换成NewDerived。 这就是为什么它显示警告。


根据错误,现在我们了解new Consumer().get()是什么问题。 它的types是something that extends baseset(new Derived(), new Consumer().get()); 寻找一个将参数作为Derived (or any super class of it), something that extends Base

现在你的两个方法都有资格获得第一个参数。 根据第二个参数something that extends base ,再次都符合这样的东西可以派生或扩展派生或扩展集合。 这就是为什么Java8引发错误。

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

 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,指出我的答案不完整。