【问题标题】:Returning null as an int permitted with ternary operator but not if statement使用三元运算符将 null 作为 int 返回,但 if 语句不允许
【发布时间】:2011-12-27 06:57:48
【问题描述】:

我们看下面sn-p中的简单Java代码:

public class Main {

    private int temp() {
        return true ? null : 0;
        // No compiler error - the compiler allows a return value of null
        // in a method signature that returns an int.
    }

    private int same() {
        if (true) {
            return null;
            // The same is not possible with if,
            // and causes a compile-time error - incompatible types.
        } else {
            return 0;
        }
    }

    public static void main(String[] args) {
        Main m = new Main();
        System.out.println(m.temp());
        System.out.println(m.same());
    }
}

在这个最简单的 Java 代码中,temp() 方法不会发出编译器错误,即使函数的返回类型是 int,我们正试图返回值 null(通过语句 return true ? null : 0; )。编译时,这显然会导致运行时异常NullPointerException

但是,如果我们用if 语句(如在same() 方法中)表示三元运算符,似乎同样的事情是错误的,确实会发出编译时错误!为什么?

【问题讨论】:

  • 另外,int foo = (true ? null : 0)new Integer(null) 都可以正常编译,第二个是自动装箱的显式形式。
  • @Izkata 这里的问题是让我理解为什么编译器试图将null 自动装箱到Integer... 这对我来说就像“猜测”或“让事情正常进行” “...
  • ...嗯,我想我在那里有答案,因为 Integer 构造函数(我发现的文档用于自动装箱)允许将字符串作为参数(可以是空值)。但是,他们还说构造函数的行为与方法 parseInt() 相同,后者会在传递 null 时抛出 NumberFormatException...
  • @Izkata - Integer 的 String 参数 c'tor 不是自动装箱操作。字符串不能自动装箱为整数。 (函数Integer foo() { return "1"; } 不会编译。)
  • 酷,学习了有关三元运算符的新知识!

标签: java nullpointerexception conditional-operator autoboxing


【解决方案1】:

编译器将null 解释为对Integer 的空引用,为条件运算符应用自动装箱/拆箱规则(如Java Language Specification, 15.25 中所述),然后愉快地继续前进。这将在运行时生成一个NullPointerException,您可以通过尝试来确认。

【讨论】:

  • 鉴于您发布的 Java 语言规范的链接,您认为在上述问题的情况下会执行哪一点?最后一个(因为我还在努力理解capture conversionlub(T1,T2))??另外,真的可以将装箱应用于空值吗?这不就像“猜测”吗??
  • ´@Gevorg 空指针是指向所有可能对象的有效指针,因此不会发生任何坏事。编译器只是假设 null 是一个 Integer,然后它可以自动装箱为 int。
  • @Gevorg - 查看 nowaq 的评论和我对他帖子的回复。我认为他选择了正确的条款。 lub(T1,T2) 是 T1 和 T2 的类型层次结构中最具体的引用类型。 (它们都至少共享 Object,所以总是有一个最具体的引用类型。)
  • @Gevorg - null 没有装箱成整数,它被解释作为对整数的引用(空引用,但这不是问题)。没有 Integer 对象是从 null 构造的,因此 NumberFormatException 没有理由。
  • @Gevorg - 如果您查看rules for boxing conversion,并将它们应用于null(这不是原始数字类型),适用的子句是“如果 p 是任何其他类型的值,装箱转换等价于恒等转换”。所以nullInteger 的装箱转换产生null,而不调用任何Integer 构造函数。
【解决方案2】:

我认为,Java 编译器将true ? null : 0 解释为Integer 表达式,可以隐式转换为int,可能给出NullPointerException

对于第二种情况,表达式null 是特殊的null 类型 see,因此代码return null 会导致类型不匹配。

【讨论】:

  • 我认为这与自动装箱有关?大概第一个返回值在 Java 5 之前不会编译,对吧?
  • @Michael 如果您将 Eclipse 的合规级别设置为 pre-5,情况似乎就是这样。
  • @Michael:这绝对看起来像自动装箱(我对 Java 还很陌生,无法做出更明确的声明——抱歉)。
  • @Vlad 编译器如何最终将true ? null : 0 解释为Integer?通过自动装箱 0 先??
  • @Gevorg:看here否则,第二个和第三个操作数分别是 S1 和 S2 类型。令 T1 为对 S1 应用装箱转换产生的类型,令 T2 为对 S2 应用装箱转换产生的类型。 和以下文本。
【解决方案3】:

其实在Java Language Specification里都有解释。

条件表达式的类型确定如下:

  • 如果第二个和第三个操作数的类型相同(可能是 null 类型),那么这就是条件表达式的类型。

因此,(true ? null : 0) 中的“null”获取一个 int 类型,然后自动装箱为 Integer。

尝试这样的事情来验证这个(true ? null : null),你会得到编译器错误。

【讨论】:

  • 但是规则的那个子句不适用:第二个和第三个操作数 not 具有相同的类型。
  • 那么答案似乎在以下语句中: > 否则,第二个和第三个操作数分别是 S1 和 S2 类型。令 T1 为对 S1 应用装箱转换产生的类型,令 T2 为对 S2 应用装箱转换产生的类型。条件表达式的类型是将捕获转换(第 5.1.10 节)应用于 lub(T1, T2)(第 15.12.2.7 节)的结果。
  • 我认为这是适用的条款。然后它尝试应用自动拆箱以便从函数返回一个int 值,这会导致 NPE。
  • @nowaq 我也这么认为。但是,如果您尝试使用new Integer(null);null 显式装箱到Integer “让T1 成为将装箱转换应用于S1 的类型...”您将得到NumberFormatException,但情况并非如此...
  • @Gevorg 我认为由于在进行拳击时发生异常,我们在这里没有得到任何结果。编译器只需要生成遵循它所做定义的代码——我们只是在完成之前得到异常。
【解决方案4】:

if 语句的情况下,null 引用不被视为Integer 引用,因为它没有参与强制它被解释为这样的表达式 .因此,该错误很容易在编译时被捕获,因为它更明显是一个 type 错误。

关于条件运算符,Java 语言规范第 15.25 节“条件运算符? :”在关于如何应用类型转换的规则中很好地回答了这个问题:

  • 如果第二个和第三个操作数的类型相同(可能是 null type),那么这就是条件表达式的类型。

    不适用,因为null 不是int

  • 如果第二个和第三个操作数之一是布尔类型并且 其他为布尔类型,则条件表达式的类型为布尔。

    不适用,因为nullint 都不是booleanBoolean

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

    不适用,因为 null 是 null 类型,但 int 不是引用类型。

  • 否则,如果第二个和第三个操作数具有可转换的类型 (§5.1.8) 到数字类型,那么有几种情况:[…]

    适用:null 被视为可转换为数字类型,并在 §5.1.8 中定义“拆箱转换”抛出NullPointerException

【讨论】:

  • 如果0 自动装箱为Integer,那么编译器将执行Java 语言规范中描述的“三元运算符规则”的最后一种情况。如果这是真的,那么我很难相信它会跳转到具有 null 和引用类型的相同规则的案例 3,这使得三元运算符的返回值成为引用类型(整数).. .
  • @Gevorg - 为什么很难相信三元运算符返回Integer?这正是正在发生的事情。 NPE 是通过尝试将表达式值拆箱以从函数返回 int 来生成的。更改函数以返回Integer,它将毫无问题地返回null
  • @TedHopp:Gevorg 正在回复我的答案的早期版本,这是不正确的。您应该忽略差异。
  • @JonPurdy “如果类型是数字类型,或者它是可以通过取消装箱转换转换为数字类型的引用类型,则称该类型可转换为数字类型”,我不不要认为null 属于这一类。此外,我们将进入“否则,应用二进制数字提升(§5.6.2)......请注意,二进制数字提升执行拆箱转换(§5.1.8)......”步骤以确定返回类型。但是拆箱转换会产生 NPE,这只会在运行时发生,而不是在尝试确定三元运算符类型时发生。我还是一头雾水。。
  • @Gevorg:拆箱发生在运行时。 null 被视为具有 int 类型,但实际上等同于 throw new NullPointerException(),仅此而已。
【解决方案5】:

首先要记住的是,Java 三元运算符有一个“类型”,无论第二个或第三个参数的实际/真实类型是什么,编译器都会确定并考虑这一点。根据几个因素,三元运算符类型以不同的方式确定,如Java Language Specification 15.26 中所示

在上面的问题中,我们应该考虑最后一种情况:

否则,第二个和第三个操作数的类型分别为 S1S2。令 T1 为将装箱转换应用于 S1 的类型,令 T2 为将装箱转换应用于 的类型>S2。条件表达式的类型是将捕获转换(第 5.1.10 节)应用到 lub(T1, T2)(第 15.12.2.7 节)的结果。

看看applying capture conversion (§5.1.10),尤其是lub(T1, T2),这是迄今为止最复杂的情​​况。

用简单的英语,经过极度简化后,我们可以将这个过程描述为计算第二个和第三个参数的“最不常见的超类”(是的,想想 LCM)。这将为我们提供三元运算符“类型”。同样,我刚才所说的是一种极端的简化(考虑实现多个通用接口的类)。

例如,如果您尝试以下操作:

long millis = System.currentTimeMillis();
return(true ? new java.sql.Timestamp(millis) : new java.sql.Time(millis));

您会注意到条件表达式的结果类型是 java.util.Date,因为它是 Timestamp/Time 对的“最少公共超类”。

由于null 可以自动装箱到任何东西,“最不常见的超类”是Integer 类,这将是上面条件表达式(三元运算符)的返回类型。然后返回值将是一个 Integer 类型的空指针,这就是三元运算符将返回的值。

在运行时,当 Java 虚拟机拆箱时,Integer 会抛出 NullPointerException。发生这种情况是因为 JVM 尝试调用函数 null.intValue(),其中 null 是自动装箱的结果。

在我看来(因为我的观点不在 Java 语言规范中,所以无论如何很多人都会发现它是错误的)编译器在评估问题中的表达式方面做得很差。鉴于您编写了true ? param1 : param2,编译器应该立即确定第一个参数 -null- 将被返回,并且它应该生成编译器错误。这有点类似于您编写 while(true){} etc... 并且编译器抱怨循环下方的代码并使用 Unreachable Statements 标记它。

你的第二种情况很简单,这个答案已经太长了......;)

更正:

经过另一次分析,我认为我说 null 值可以装箱/自动装箱是错误的。谈到 Integer 类,显式装箱包括调用 new Integer(...) 构造函数或 Integer.valueOf(int i);(我在某处找到了这个版本)。前者会抛出一个NumberFormatException(这不会发生),而第二个则没有意义,因为int 不能是null...

【讨论】:

  • OP 原代码中的null 没有装箱。它的工作方式是:编译器假定null 是对整数的引用。使用三元表达式类型的规则,它决定整个表达式是一个整数表达式。然后它生成代码来自动装箱1(以防条件计算为false)。在执行期间,条件的计算结果为true,因此表达式的计算结果为null。当试图从函数返回 int 时,null 被取消装箱。然后抛出一个 NPE。 (编译器可能会优化大部分内容。)
【解决方案6】:

实际上,在第一种情况下,表达式可以被计算,因为编译器知道,它必须被计算为Integer,但是在第二种情况下,返回值的类型(null)不能是确定,所以不能编译。如果将其转换为Integer,代码将编译。

【讨论】:

    【解决方案7】:
    private int temp() {
    
        if (true) {
            Integer x = null;
            return x;// since that is fine because of unboxing then the returned value could be null
            //in other words I can say x could be null or new Integer(intValue) or a intValue
        }
    
        return (true ? null : 0);  //this will be prefectly legal null would be refrence to Integer. The concept is one the returned
        //value can be Integer 
        // then null is accepted to be a variable (-refrence variable-) of Integer
    }
    

    【讨论】:

      【解决方案8】:

      这个怎么样:

      public class ConditionalExpressionType {
      
          public static void main(String[] args) {
      
              String s = "";
              s += (true ? 1 : "") instanceof Integer;
              System.out.println(s);
      
              String t = "";
              t += (!true ? 1 : "") instanceof String;
              System.out.println(t);
      
          }
      
      }
      

      输出为真,真。

      Eclipse 颜色编码条件表达式中的 1 为自动装箱。

      我的猜测是编译器将表达式的返回类型视为 Object。

      【讨论】:

        猜你喜欢
        • 2017-02-14
        • 2018-12-26
        • 2016-05-22
        • 1970-01-01
        • 2012-08-28
        • 2015-01-07
        • 1970-01-01
        • 2021-07-09
        • 1970-01-01
        相关资源
        最近更新 更多