Java 类型推断:引用在 Java 8 中是不明确的,但在 Java 7 中不是
Java type inference: reference is ambiguous in Java 8, but not Java 7
假设我们有 2 个 classes。一个空 class Base
,以及这个 class Derived
.
的一个子 class
public class Base {}
public class Derived extends Base {}
然后我们在另一个class:
中有几个方法
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");
}
}
这个在Java7编译运行成功,但是在Java8编译不成功。错误:
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>
怎么可能 匹配 Collection?
问题是类型推断已经改进。你有一个像
这样的方法
public <T extends Base> T get() {
return (T) new Derived();
}
基本上是说,“调用者可以决定 Base
我 return 的子类”,这显然是胡说八道。每个编译器都应该在这里给你一个关于你的类型转换的未经检查的警告 (T)
。
现在你有一个方法调用:
set(new Derived(), new Consumer().get());
回想一下你的方法 Consumer.get()
说“调用者可以决定我 return”。因此,假设可能存在一个扩展 Base
并同时实现 Collection
的类型是完全正确的。所以编译器说“我不知道是调用 set(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
方法内部。
所以正确的解决方法是从 get
方法中删除类型参数并声明它 really returns, Derived
.
顺便说一句,令我恼火的是,您声称此代码可以在 Java 7 下编译。它对嵌套方法调用的有限类型推断导致将 get
方法处理为嵌套的调用上下文,如 returning 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
的签名的话。它 returns 我们是一个类型 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 in thread "main" java.lang.ClassCastException:
com.company.Derived cannot be cast to com.company.NewDerived
因为 get
returns 一个 Derived
类型的对象,但你已经向编译器提到它是 NewDerived
类型的对象。而且它不能正确地将 Derived 转换为 NewDerived。这就是它显示警告的原因。
根据错误,现在我们明白了 new Consumer().get()
出了什么问题。它的类型是 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,它的类型推断有点弱。所以它尝试做类似的事情,
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 指出我的回答不完整。
假设我们有 2 个 classes。一个空 class Base
,以及这个 class Derived
.
public class Base {}
public class Derived extends Base {}
然后我们在另一个class:
中有几个方法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");
}
}
这个在Java7编译运行成功,但是在Java8编译不成功。错误:
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>
怎么可能 匹配 Collection?
问题是类型推断已经改进。你有一个像
这样的方法public <T extends Base> T get() {
return (T) new Derived();
}
基本上是说,“调用者可以决定 Base
我 return 的子类”,这显然是胡说八道。每个编译器都应该在这里给你一个关于你的类型转换的未经检查的警告 (T)
。
现在你有一个方法调用:
set(new Derived(), new Consumer().get());
回想一下你的方法 Consumer.get()
说“调用者可以决定我 return”。因此,假设可能存在一个扩展 Base
并同时实现 Collection
的类型是完全正确的。所以编译器说“我不知道是调用 set(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
方法内部。
所以正确的解决方法是从 get
方法中删除类型参数并声明它 really returns, Derived
.
顺便说一句,令我恼火的是,您声称此代码可以在 Java 7 下编译。它对嵌套方法调用的有限类型推断导致将 get
方法处理为嵌套的调用上下文,如 returning 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
的签名的话。它 returns 我们是一个类型 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 in thread "main" java.lang.ClassCastException: com.company.Derived cannot be cast to com.company.NewDerived
因为 get
returns 一个 Derived
类型的对象,但你已经向编译器提到它是 NewDerived
类型的对象。而且它不能正确地将 Derived 转换为 NewDerived。这就是它显示警告的原因。
根据错误,现在我们明白了 new Consumer().get()
出了什么问题。它的类型是 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,它的类型推断有点弱。所以它尝试做类似的事情,
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 指出我的回答不完整。