【问题标题】:Compilers behave differently with a null parameter of a generic method编译器对泛型方法的 null 参数表现不同
【发布时间】:2011-03-01 07:41:51
【问题描述】:

以下代码用 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

找到:&lt;P,T&gt;void

必填:无效

       doIt(null);
           ^

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

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

【问题讨论】:

    标签: java eclipse generics null compilation


    【解决方案1】:

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

    您可以致电doIt((Number) null); 解决此问题

    即使您不打算使用 javac 进行开发,也请修复此问题,因为 ant 或 maven 等工具使用它,如果您在某些时候引入它们会导致问题。

    【讨论】:

    • 我记得过去在 Java 中必须使用带有 null 的强制转换,但我不记得为什么......还有其他情况需要这样做吗?
    • @JAB 强制转换空值偶尔会在调用重载方法时完成。例如,如果你有两个方法,foo(String)foo(Number),你不能说foo(null),因为它是模棱两可的。通常你会强制转换 null 文字来选择你想要的方法。或者,您可以使用适当的静态类型的变量,其值恰好为空,但这更冗长,可能不是更清晰。
    • @JAB:你也可以在处理可变参数时投nullstackoverflow.com/questions/2888305/… vararg gotchas/count((Object) null);
    • @irreputable 你不同意强制转换 null 看起来不太好;)
    【解决方案2】:

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

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

    "Bug" ID 6299211 - method type variable: inference broken for null

    此程序无法编译:

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

    状态已关闭,不是缺陷。

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


    JLS 15.12.2.8 Inferring Unresolved Type Arguments

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


    但是,Object 不是Comparable&lt;? super Object&gt; 的子类型,因此不在Collections.max 声明中的类型变量范围内:

    &lt;T extendsObject &amp; Comparable&lt;? super T&gt;&gt; T max(Collection&lt;? extends T&gt;)


    进一步探索

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

    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()
    

    在任何一种情况下,调用 doIt(); 都不会在 javac 中编译,因为它必须根据 15.12.2.8 将 U 推断为 Object,即使这样做会触发编译错误。


    关于 Eclipse 的注意事项

    虽然上面的 sn-ps 都不能在某些版本的 javac 中编译,但它们都可以在某些版本的 Eclipse 中编译。这表明 Eclipse 存在错误。众所周知,不同的编译器之间存在分歧。

    相关问题

    【讨论】:

    • 我不同意这个评价。 null 不应被推断为 Object。它应该被推断为“任何匹配的东西”。
    • +1 用于查找相关的 JLS 部分。我也不同意,但这并不重要:)
    • 你不同意 JLS 是什么意思?:) Java 泛型是一个精心构建的纸牌屋,看到一张纸牌就说它放错了是不公平的。跨度>
    • java 泛型是一个卡片城堡。轻轻一击,它就破裂了。但这是另一个话题。很明显可以推断出正确的类型(eclipse做到了)。
    • 非常感谢您提供的信息。尽管我倾向于同意 Bozho 的观点,即这是 javac 中的一个错误,但我无意完全理解编译器规范,以证明/反驳这一点。这次我将使用一种解决方法,并且可能会报告一个错误(在我的问题的非简化版本中,有更多参数可用于类型推断,但仍然失败)。
    【解决方案3】:

    根据 polygenelubricants 的研究,sun 的 javac 显然忠实于规范。过去我也使用过其他编译器,当发生冲突时,总是证明 sun 的 javac 是正确的。 Sun 的优势是将他们从实施过程中获得的经验记录到规范中,而其他人则必须从头开始阅读规范 - 阅读时真的很难不入睡。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-11-23
      • 1970-01-01
      • 2012-01-02
      • 2021-08-22
      • 2017-01-28
      相关资源
      最近更新 更多