类型推断的差异JDK8 javac / Eclipse Luna?

我正在尝试将项目切换到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 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 []匹配合适,而不是Object。

这是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 

编译器只能检查方法签名,而不是方法体,因此该部分是无关紧要的。

这会将您的代码“减少”为(伪代码):

 public class TypeInferenceTest { public static String toString(Object obj); public static String toString(char[] ca); public static String toString(Throwable t); public static  U getKey(Object code, U defaultValue); public static void test() { Object code = "test"; toString(getKey(code, null)); } } 

另请注意, U getKey(...)确实是: U getKey(...)

它知道getKey(code, null)返回的是: ? extends Object ? extends Object ,因此它返回Object的子类型或Object本身。
有三个匹配的签名,即Objectchar[]Throwable ,其中char[]Throwable匹配并且优于Object ,因为你要求一个? extends Object ? extends Object

所以它不能选择哪一个是正确的,因为所有三个都匹配签名。

当您将其更改为:

 public static Object getKey(Object code, Object defaultValue); 

那么只有public static String toString(Object obj); 匹配,因为它匹配得更 ? extends Object ? extends Object不等于Object

编辑 ,我查看了问题的原始意图:为什么它在Java 7中编译,而在Java 8中编译?

在Java 8中,类型推断得到了极大的改进。

而在Java 7中,它可以例如仅推断getKey返回一个Object ,它现在在Java 8中推断它返回一个? extends Object ? 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 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 ,正是由于我上面概述的原因。

javac实际上可能是正确的。 规范写道 :

null类型有一个值,null引用,由null literal null表示,它由ASCII字符组成。

因此,null的类型是null类型。

表达式getKey(code, null)是generics方法的方法调用表达式。 规范定义了它的类型如下:

  • 如果所选方法是通用的并且方法调用不提供显式类型参数,则按照第18.5.2节中的规定推断调用类型。

类型推断算法的实际描述相当复杂,但是为U推断的类型必须可以从null类型中分配。 唉,对于所有参考类型都是如此,那么选择哪一种? 最合乎逻辑的是最具体的类型,即null类型。 因此,方法调用表达式的类型可能是null类型。

现在,方法调用表达式toString(getKey(code, null))引用哪个方法? 规范写道 :

第二步搜索上一步中为成员方法确定的类型。 此步骤使用方法名称和参数表达式来查找可访问和适用的方法,即可以在给定参数上正确调用的声明。

可能存在多于一种这样的方法,在这种情况下,选择最具体的方法。 最具体方法的描述符(签名加返回类型)是在运行时用于执行方法分派的方法。

由于参数的类型是null类型,因此所有三种toString方法都适用。 规范写道:

如果方法可访问且适用,并且没有其他适用且可访问的方法严格更具体,则称该方法对于方法调用是最大特定的。

如果只有一个最大特定方法,那么该方法实际上是最具体的方法; 它必须比适用的任何其他可访问方法更具体。 然后按照§15.12.3的规定对其进行一些进一步的编译时检查。

可能没有方法是最具体的,因为有两种或更多种方法是最具体的。 在这种情况下:

  • 如果所有最大特定方法都具有覆盖等效签名(第8.4.2节),则:

    • 如果具体的最大特定方法之一是具体的(即非抽象或默认),则它是最具体的方法。

    • 否则,如果所有最大特定方法都是抽象的或默认的,并且所有最大特定方法的签名具有相同的擦除(§4.6),那么在具有最大特定方法的子集中任意选择最具体的方法。最具体的回报类型。

      在这种情况下,最具体的方法被认为是抽象的。 此外,当且仅当在每个最大特定方法的throws子句中声明了该exception或其擦除时,才考虑使用最具体的方法抛出已检查的exception。

  • 否则,方法调用不明确,并发生编译时错误。

toString(char[])toString(Throwable)都比toString(Object)更具体,但它们都没有比另一个更具体,它们的签名也不等于等效。

因此,方法调用是模糊的,并被编译器拒绝。