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
返回Derived
types的对象,但是你已经提到编译器它是NewDerived
。 它不能正确地将Derived转换成NewDerived。 这就是为什么它显示警告。
根据错误,现在我们了解new Consumer().get()
是什么问题。 它的types是something that extends base
。 set(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,指出我的答案不完整。