【问题标题】:Conversion from null to int possible?可以从 null 转换为 int 吗?
【发布时间】:2011-07-05 21:05:08
【问题描述】:

我知道,当我阅读此问题的答案时,我会发现我忽略了一些东西 那是在我眼皮底下。但我花了最后 30 分钟试图自己弄清楚 没有结果。

所以,我在用 Java 6 编写程序时发现了一些(对我而言)奇怪的特性。 为了尝试和隔离它,我做了两个小例子。 我首先尝试了以下方法:

private static int foo()
{
    return null;
}

并且编译器拒绝了它:类型不匹配:无法从 null 转换为 int。

这对我来说很好,它尊重我熟悉的 Java 语义。 然后我尝试了以下方法:

private static Integer foo(int x)
{
    if (x < 0)
    {
        return null;
    }
    else
    {
        return new Integer(x);
    }
}

private static int bar(int x)
{
    Integer y = foo(x);

    return y == null ? null : y.intValue();
}

private static void runTest()
{
    for (int index = 2; index > -2; index--)
    {
        System.out.println("bar(" + index + ") = " + bar(index));
    }
}

编译没有错误!但是,在我看来,应该有类型转换错误 在行中

    return y == null ? null : y.intValue();

如果我运行程序,我会得到以下输出:

bar(2) = 2
bar(1) = 1
bar(0) = 0
Exception in thread "main" java.lang.NullPointerException
    at Test.bar(Test.java:23)
    at Test.runTest(Test.java:30)
    at Test.main(Test.java:36)

你能解释一下这种行为吗?

更新

非常感谢您提供的许多澄清答案。我有点担心,因为 这个例子不符合我的直觉。让我不安的一件事是 一个 null 被转换为一个 int 我想知道结果会是什么 be: 0 就像在 C++ 中一样?那会很奇怪。 好在运行时无法进行转换(空指针异常)。

【问题讨论】:

    标签: java


    【解决方案1】:

    让我们看看这一行:

    return y == null ? null : y.intValue();
    

    ? : 语句中,: 的两边必须具有相同的类型。在这种情况下,Java 将使其具有Integer 类型。 Integer 可以是null,所以左边没问题。表达式y.intValue() 的类型为int,但Java 会将其自动装箱为Integer(注意,您也可以编写y,这样可以为您节省此自动装箱)。

    现在,必须再次将结果拆箱到int,因为该方法的返回类型是int。如果您将Integer(即null)拆箱,您将获得NullPointerException

    注意:Java 语言规范的Paragraph 15.25 解释了关于? : 条件运算符的类型转换的确切规则。

    【讨论】:

    • 编译器如何尝试解析三元表达式中的类型的一般规则是什么?
    • +1,这对我来说很有意义。为什么编译器不允许第一个测试int foo() { return null; },在这种情况下,它应该自动将null 自动装箱到Integer?或者,换句话说,我想我的问题是,为什么它选择将三元运算中的字段自动装箱到Integer 并且不将它们保留为原语?该决定的依据在哪里?
    • @Oli 我不确定,但它可能有令人惊讶的行为,这就是一个例子。请参阅 JLS 的 paragraph 15.25
    • @c00kiemonster 我猜是因为太明显null 不是int,编译器不会装箱然后直接拆箱null。您遇到问题是因为 ? : 有时会违反直觉。
    【解决方案2】:

    Guava 使用MoreObjects.firstNonNull 有一个非常优雅的解决方案:

    Integer someNullInt = null;
    int myInt = MoreObjects.firstNonNull(someNullInt, 0);
    

    【讨论】:

    【解决方案3】:

    返回类型的类型由Java在这里推断。这就是问题..

    http://java.sun.com/docs/books/jls/third_edition/html/expressions.html#15.25

    这是实际的问题--

    如果第二个和第三个操作数之一是空类型,另一个是引用类型,那么条件表达式的类型就是那个引用类型。

    所以,基本上编译器将条件表达式的返回类型推断为整数,这就是它允许您成功编译的原因。

    编辑:查看 cmets 中的规则

    【讨论】:

    • @Kai:现在我开始明白了:编译器决定了 ? 的结果类型。运算符首先找到整数。只有这样,它才会尝试将 Integer 与返回类型 int 进行匹配。如果执行拆箱,它们是兼容的。它永远不会看到 null 不能拆箱为 int。在第一个示例中,只有一个转换步骤,这就是编译器说类型不兼容的原因。
    • @Giorgio -- 是的。如果您查看链接,则会有进一步的说明——它将两个操作数装箱,然后应用相同的逻辑(在这种情况下, int 被装箱为 Integer ).. 返回类型假定为 Integer。 **否则,第二个和第三个操作数分别为 S1 和 S2 类型。令 T1 为对 S1 应用装箱转换产生的类型,令 T2 为对 S2 应用装箱转换产生的类型。条件表达式的类型是将捕获转换(第 5.1.10 节)应用于 lub(T1, T2)(第 15.12.2.7 节)的结果。
    • "而另一个的类型是引用类型"
    【解决方案4】:

    如果您的项目中没有 Guava,但已经在使用 Apache Commons,您可以使用 Apache Lang3 及其 ObjectUtils 类。

    用法与Guava基本相同:

    Integer number = null;
    int notNull = ObjectUtils.firstNonNull(number, 0);
    

    请注意,Guava 库中的此方法比 Apache 中的运行速度更快。 这是我刚刚在笔记本电脑(Core i7-7500U 2.7 GHz)、Oracle Java 8、多次运行、JVM 预热、结果取平均值时进行的简短比较:

    ╔══════════════╦══════╦══════╦════════╦══════╗
    ║ Library/Runs ║ 1000 ║ 1mln ║ 100mln ║ 1bln ║
    ╠══════════════╬══════╬══════╬════════╬══════╣
    ║ Apache       ║    1 ║   30 ║    782 ║ 9981 ║
    ║ Guava        ║    1 ║   22 ║    120 ║  828 ║
    ╚══════════════╩══════╩══════╩════════╩══════╝
    

    结果以毫秒为单位。 我不认为你经常需要运行这种方法数十亿次,但是,进行性能比较总是好的

    【讨论】:

      【解决方案5】:

      这说明了人类读取代码的方式和编译器读取代码的方式之间存在问题的差异。

      看到一个三元表达式时,你很有可能在心理上把它分成两部分,就像if/else 语句一样:

      if (y == null)
          return null;
      else
          return y.intValue();
      

      可以看到这是无效的,因为它会导致一个可能的分支,其中定义为返回 int 的方法实际上返回的是 null(非法!)。

      编译器看到的是一个表达式,它必须有一个类型。它注意到三元运算包括一侧的null 和另一侧的int;由于 Java 的自动装箱行为,它会得出关于表达式类型是什么的“最佳猜测”(我的术语,而不是 Java 的):Integer(这是公平的:它是唯一合法的类型null或盒装的int)。

      由于该方法应该返回 int,因此从编译器的角度来看这很好:返回的表达式的计算结果为 Integer,可以自动取消装箱。

      【讨论】:

        【解决方案6】:

        自动拆箱null 值的问题可能真的很烦人。在您的示例中,它是三元运算符结果类型推断和自动拆箱的组合(应该咨询 JLS 为什么它的行为如此)

        但通常,您应该尽量避免使用包装类型。使用int 而不是Integer。如果您需要一个表示“无结果”的特殊值,则可以使用 Integer.MAX_VALUE 为例。

        【讨论】:

        • 这就是为什么我有一个返回类型 int。 null 来自复制和粘贴,当我看到编译器没有抱怨时,我很惊讶。
        猜你喜欢
        • 2010-10-25
        • 1970-01-01
        • 2011-01-16
        • 2013-01-10
        • 1970-01-01
        • 2016-10-30
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多