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

时间:2014-07-21 14:55:26

标签: java eclipse javac type-inference

我试图将项目切换到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

2 个答案:

答案 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,因此它会返回ObjectObject本身的子类型。
有三个匹配的签名,即Objectchar[]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)更具体,但两者都没有比另一个更具体,他们的签名也不等于等同。

因此,方法调用不明确,并被编译器拒绝。