编译器使用泛型方法的 null 参数时行为不同

2022-09-03 06:01:19

下面的代码使用 Eclipse 完美编译,但无法使用 javac 进行编译:

public class HowBizarre {
      public static <P extends Number, T extends P> void doIt(P value) {
      }

      public static void main(String[] args) {
            doIt(null);
      }
}

我简化了代码,所以现在根本不使用T。不过,我没有看到错误的原因。出于某种原因,javac 决定 T 代表 Object,然后抱怨 Object 不符合 T 的边界(这是真的):

HowBizarre.java:6:不兼容的类型;推断类型参数 java.lang.Number,java.lang.Object 不符合类型变量 (s) P,T 的边界

已找到 : 无效<P,T>

必需:无效

       doIt(null);
           ^

请注意,如果我将 null 参数替换为非 null 值,则编译正常。

哪些编译器行为正确,为什么?这是其中之一的错误吗?


答案 1

问题在于 JLS 规范要求将原本无法推断的类型参数推断为 ,即使它不满足边界(因此会触发编译错误)。Object

以下是“bug”报告的摘录(为清楚起见,该报告已进一步注释):

“错误”ID 6299211 - 方法类型变量:因 null 而中断的推理

此程序不编译:

public class Try {
    void m() {
        java.util.Collections.max(null);
    }
}

状态已关闭,不是缺陷。

评估这不是一个错误。推理算法无法从参数 () 中收集任何信息,并且不会在对返回值有任何期望的地方调用该方法。在这种情况下,编译器必须推断类型变量。nulljava.lang.Object


JLS 15.12.2.8 推断未解析的类型参数

然后推断出尚未推断的任何剩余类型变量具有类型Object


但是,不是 的子类型,因此不在声明中的类型变量的范围内:ObjectComparable<? super Object>Collections.max

<T 扩展了 Object & Comparable<? super T>> T max(Collection<? 扩展了 T>)


进一步探索

使用显式类型参数“修复”了问题:

HowBizarre.<Number,Integer>doIt(null); // compiles fine in javac

为了表明这与参数关系不大,而与类型推断的绝对缺乏信息有关,您可以尝试例如以下任何一种声明:null

<T,U extends Comparable<T>> void doIt()

<T extends Number,U extends T> void doIt()

在任何一种情况下,调用都不会编译 ,因为它必须推断为按照 15.12.2.8 进行编译,即使这样做会触发编译错误。doIt();javacUObject


关于日食的说明

虽然上面的片段都没有在某个版本的 中编译,但它们在某个版本的 Eclipse 中都编译。这表明Eclipse存在一个错误。众所周知,不同的编译器之间存在分歧。javac

相关问题


答案 2

这更像是javac中的一个错误。Eclipse 推断出正确的类型。

您可以通过致电来解决它doIt((Number) null);

即使您不打算使用javac进行开发,也可以解决此问题,因为像ant或maven这样的工具会使用它,如果您在某个时候引入它们,它会导致问题。