我试图将项目切换到Java8,并遇到Eclipse Luna和javac的类型推断之间的奇怪差异。使用JDK 1.7.0_65 javac,这段代码编译得很好。 JDK 1.8.0_11抱怨toString(char [])和toString(Throwable)都匹配" toString(getKey(code,null));"线。 Eclipse Luna 4.4(I20140606-1215)使用JDK快乐地编译它:
public class TypeInferenceTest {
public static String toString(Object obj) {
return "";
}
public static String toString(char[] ca) {
return "";
}
public static String toString(Throwable t) {
return "";
}
public static <U> U getKey(Object code, U defaultValue) {
return defaultValue;
}
public static void test() {
Object code = "test";
toString(getKey(code, null));
}
}
我认为唯一可能匹配的签名是toString(Object)。
当然我可以简单地向Object添加一个强制转换,但我想知道为什么 javac不能自己推断类型(虽然eclipse会这样做),以及为什么heck javac会考虑Throwable和char []匹配,但不是对象。
这是Eclipse或javac中的错误吗? (我的意思是只有一个编译器可以在这里,无论是编译还是不编译
编辑:来自javac(JDK8)的错误消息:
C:\XXXX\Workspace\XXXX\src>javac -cp . TypeInferenceTest.java
TypeInferenceTest.java:22: error: reference to toString is ambiguous
toString(getKey(code, null));
^
both method toString(char[]) in TypeInferenceTest and method toString(Throwable) in TypeInferenceTest match
1 error
答案 0 :(得分:3)
编译器只能检查方法签名,而不是方法体,因此该部分无关紧要。
将您的代码“减少”为(伪代码):
public class TypeInferenceTest {
public static String toString(Object obj);
public static String toString(char[] ca);
public static String toString(Throwable t);
public static <U> U getKey(Object code, U defaultValue);
public static void test() {
Object code = "test";
toString(getKey(code, null));
}
}
另请注意,<U> U getKey(...)
确实是:<U extends Object> U getKey(...)
。
所有它知道getKey(code, null)
返回的是:? extends Object
,因此它会返回Object
或Object
本身的子类型。
有三个匹配的签名,即Object
,char[]
和Throwable
,其中char[]
和Throwable
匹配均等且优于Object
,因为您要求? extends Object
。
所以它无法选择哪一个是正确的,因为所有三个都匹配签名。
将其更改为:
public static Object getKey(Object code, Object defaultValue);
然后只有public static String toString(Object obj);
匹配,因为它与更好匹配的任何其他? extends Object
不等于Object
。
编辑,我查看了问题的原始意图:为什么要在Java 7中编译,而不是在Java 8中编译?
在Java 8中,类型推断得到了极大的改进。
然而,在Java 7中,它可以仅推断getKey
返回Object
,现在它在Java 8中推断它返回? extends Object
。
使用Java 7时,只有一个匹配,即Object
。
要让更改可视化更好,请考虑以下代码:
public class TypeInferenceTest {
public static String toString(Object obj) { return "1"; }
public static String toString(Throwable t) { return "2"; }
public static <U> U getKey(Object code, U defaultValue) { return defaultValue; }
public static void test() {
Object code = "test";
String result = toString(getKey(code, null));
System.out.println(result);
}
public static void main(String[] args) {
test();
}
}
在Java 7上,它打印1
,在Java 8上打印2
,正是由于我上面概述的原因。
答案 1 :(得分:1)
javac实际上可能是正确的。规范writes:
null类型有一个值,空引用,由null文字null表示,由ASCII字符组成。
因此,null
的类型为空类型。
表达式getKey(code, null)
是泛型方法的方法调用表达式。规范defines的类型如下:
- 如果所选方法是通用的且方法调用不提供显式类型参数,则按照§18.5.2中的规定推断调用类型。
类型推断算法的实际描述相当复杂,但是U
的推断类型必须可以从null类型中分配。唉,所有参考类型都是如此,那么选择哪一种?最合乎逻辑的是最具体的类型,即null类型。因此,方法调用表达式的类型可能是null类型。
现在,方法调用表达式toString(getKey(code, null))
引用了哪个方法?规范writes:
第二步搜索上一步中为成员方法确定的类型。此步骤使用方法的名称和参数表达式来查找可访问和适用的方法,即可以在给定参数上正确调用的声明。
可能存在多种此类方法,在这种情况下,选择最具体的方法。最具体方法的描述符(签名加返回类型)是在运行时用于执行方法调度的方法。
由于参数的类型是null类型,因此所有三种toString
方法都适用。规范写道:
如果方法调用是可访问且适用的,并且没有其他方法适用且可访问且严格更具体,则该方法被称为最大程度地特定于方法调用。
如果只有一个最大特定方法,那么该方法实际上是最具体的方法;它必须比适用的任何其他可访问方法更具体。然后按照§15.12.3中的规定对其进行一些进一步的编译时检查。
有可能没有最具体的方法,因为有两种或更多种方法是最具体的。在这种情况下:
如果所有最具体的特定方法都具有覆盖等效签名(§8.4.2),那么:
如果具体的一个最具体的方法是具体的(即非抽象或默认),则它是最具体的方法。
否则,如果所有最大特定方法都是抽象的或默认的,并且所有最大特定方法的签名都具有相同的擦除(§4.6),那么最具体的方法是在子集中任意选择的。具有最特定返回类型的最大特定方法。
在这种情况下,最具体的方法被认为是抽象的。此外,当且仅当在每个最大特定方法的throws子句中声明了该异常或其擦除时,才会考虑使用最具体的方法抛出已检查的异常。
否则,方法调用不明确,并发生编译时错误。
toString(char[])
和toString(Throwable)
都比toString(Object)
更具体,但两者都没有比另一个更具体,他们的签名也不等于等同。
因此,方法调用不明确,并被编译器拒绝。