当前位置: 首页 > 知识库问答 >
问题:

类型推断的差异 JDK8 爪哇/日食月神?

陶飞鸿
2023-03-14

我正在尝试将一个项目切换到Java8,并在日食月神和javac的类型推断之间遇到了奇怪的差异。使用JDK 1.7.0_65 javac,这段代码编译得很好。JDK 1.8.0_11 抱怨 toString(字符[]) 和 toString(可抛出)都匹配“toString(getKey(代码,空)]”行。日食 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可以),为什么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

共有2个答案

习洲
2023-03-14

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

null类型有一个值,即null引用,由null文本null表示,该文本由ASCII字符组成。

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

表达式 getKey(代码,空)是泛型方法的方法调用表达式。规范定义其类型如下:

  • 如果选择的方法是泛型的并且方法调用不提供显式类型参数,则推断调用类型如§18.5.2中所述。

类型推断算法的实际描述相当复杂,但为 U 推断的类型必须可从 null 类型中赋值。唉,这适用于所有引用类型,那么选择哪一个呢?最合乎逻辑的一个是最具体的此类类型,即 null 类型。因此,方法调用表达式的类型可能是 null 类型。

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

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

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

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

如果一个方法是可访问和适用的,并且没有其他适用和可访问的方法是严格更具体的,则该方法被称为对方法调用具有最大特异性。

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

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

>

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

    > < li>

    如果其中一个最具体的方法是具体的(即非抽象的或默认的),则它是最具体的方法。

    否则,如果所有最大特定方法是抽象的或缺省的,并且所有最大特定方法的签名具有相同的擦除(4.6),则在具有最特定返回类型的最大特定方法的子集中任意选择最特定的方法。

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

    否则,方法调用是不明确的,并且会发生编译时错误。

    toString(char[])toString(Throwable)都比toString(Object)更具体,但两者都不是比另一个更具体,它们的签名也不是覆盖等价的。

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

  • 司徒寒
    2023-03-14

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

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

    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));
        }
    }
    

    还要注意

    它只知道getKey(code, null)返回的是:?扩展对象,因此它返回Object的子类型,或Object本身。
    有三个签名匹配,即Objectchar[]Throwable,其中char[]Throwable匹配相同且优于Object,因为您要求了?扩展对象

    因此,它无法选择哪个是正确的,因为所有三个都与签名匹配。

    当您将其更改为:

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

    然后只有public静态String toString(Object obj);匹配,因为它与任何其他匹配得更好?扩展不等于Object的Object

    编辑,我看了一下问题的本意:为什么在Java 7中可以编译,而在Java 8中不能?

    Java8类推理得到了很大的改进。

    例如,在 Java 7 中,它只能推断 getKey 返回了一个对象,而现在在 Java 8 中,它推断它返回了一个 ? 扩展对象

    当使用Java7时,只有一个匹配项,即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();
        }
    }
    

    在Java7它打印1,在Java8它打印2,正是由于我上面概述的原因。

     类似资料:
    • 以下代码可以用javac和Eclipse 4.6.1/4.6编译,但在Eclipse 4.6.2中会产生一个错误: Eclipse 4.6.2 在 下抱怨存在类型不匹配:无法从 int 转换为可比较 我认为的参数应该是

    • 我试图在macOS高Sierra上的eclipse氧气4.7.3a上使用windows Builder。它安装良好,但每次我尝试打开设计部分时,它都会显示此错误 Eclipse在0下运行,但是这个Java项目有1.8Java的合规级别,因此WindowBuilder将无法从该项目加载类。对项目使用较低级别的Java,或者使用较新的Java版本运行Eclipse。 我什么都试过了。就像改变运行环境,

    • 我已经在网站上看到了“解决方案”http://www.rgagnon.com/javadetails/java-0506.html,但它不能正常工作。昨天(六月八日)应该是159,但它说是245。 那么,有没有人用Java解决方案来获取当前日期的三位数朱利安日(不是朱利安日——我需要今年的日期)? 谢谢!马克

    • 为什么可以推断闭包表达式的参数类型和返回类型,而不是rust中的函数?

    • 我正在尝试运行以下代码,由于类型推断,它在JDK8下编译得很好: 但是,运行此命令会抛出ClassCastException:CB无法强制转换为[Ljava.lang.Object,但CB b=convert(a)可以正常工作。 知道为什么吗?

    • 几周前,我切换到Eclipse Oxygen,并开始出现如下错误。 project facet JST.AppClient的8.0版本不存在。 eclipse.buildid=4.7.1.m20171009-0410 java.version=1.8.0_144 java.vendor=Oracle Corporation BootLoader常量:OS=win32,arch=x86_64,ws=