当前位置: 首页 > 面试题库 >

JDK8和JDK10上三元运算符的行为差异

阎淮晨
2023-03-14
问题内容

考虑以下代码

public class JDK10Test {
    public static void main(String[] args) {
        Double d = false ? 1.0 : new HashMap<String, Double>().get("1");
        System.out.println(d);
    }
}

在JDK8上运行时,此代码会打印,null而在JDK10上,此代码会导致NullPointerException

Exception in thread "main" java.lang.NullPointerException
    at JDK10Test.main(JDK10Test.java:5)

编译器产生的字节码几乎与JDK10编译器产生的两个附加指令几乎相同,这两个指令与自动装箱有关,并且似乎对NPE负责。

15: invokevirtual #7                  // Method java/lang/Double.doubleValue:()D
18: invokestatic  #8                  // Method java/lang/Double.valueOf:(D)Ljava/lang/Double;

此行为是JDK10中的错误还是为了使行为更严格而进行的有意更改?

JDK8:  java version "1.8.0_172"
JDK10: java version "10.0.1" 2018-04-17

问题答案:

我相信这是一个似乎已修复的错误。NullPointerException根据JLS的说法,抛出a 似乎是正确的行为。

我认为这里发生的是由于版本8中的某种原因,编译器考虑了方法的返回类型而不是实际类型参数所提及的类型变量的范围。换句话说,它认为...get("1")return
Object。这可能是因为它正在考虑该方法的擦除或其他一些原因。

行为应取决于get方法的返回类型,如以下第15.26节摘录所指定:

  • 如果第二和第三操作数表达式都是 数字 表达式,则条件表达式是数字条件表达式。

为了对条件进行分类,以下表达式是数字表达式:

* […]

* **方法调用表达式(第15.12节),为其选择的最特定的方法(第15.12.2.5节)具有可转换为数字类型的返回类型。**

请注意,对于泛型方法,这是实例化方法的类型参数之前的类型。

* […]
  • 否则,条件表达式是参考条件表达式。

[…]

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

  • […]

  • 如果第二个和第三个操作数之一是原始类型T,而另一个操作数的类型是将装箱转换(第5.1.7节)应用于的结果T,则条件表达式的类型为T

换句话说,如果两个表达式都可转换为数字类型,并且一个表达式是原始类型,而另一个则被装箱,则三元条件的结果类型将是原始类型。

(表15.25-C还方便地向我们显示了三元表达式的类型boolean ? double : Double的确是double,再次表示拆箱和投掷是正确的。)

如果该get方法的返回类型不能转换为数字类型,则三元条件将被视为“引用条件表达式”,并且不会发生拆箱。

另外,我认为注释 “对于通用方法,这是在实例化方法的类型参数之前的类型”
不适用于我们的情况。Map.get没有声明类型变量,因此它不是JLS定义的通用方法。但是,此注释
在Java 9
添加的(是唯一的更改,请参阅JLS8),因此它可能与我们今天看到的行为有关。

对于a HashMap<String, Double>,返回类型get Double

这是支持我的理论的MCVE,即编译器正在考虑类型变量范围,而不是实际的类型参数:

class Example<N extends Number, D extends Double> {
    N nullAsNumber() { return null; }
    D nullAsDouble() { return null; }

    public static void main(String[] args) {
        Example<Double, Double> e = new Example<>();

        try {
            Double a = false ? 0.0 : e.nullAsNumber();
            System.out.printf("a == %f%n", a);
            Double b = false ? 0.0 : e.nullAsDouble();
            System.out.printf("b == %f%n", b);

        } catch (NullPointerException x) {
            System.out.println(x);
        }
    }
}

该程序在Java 8上的输出为:

a == null
java.lang.NullPointerException

换句话说,尽管e.nullAsNumber()e.nullAsDouble()具有相同的实际返回类型,只有e.nullAsDouble()被认为是一个“数字表达”。方法之间的唯一区别是类型变量绑定。

可能还有更多的调查可以做,但是我想发表我的发现。我尝试了很多事情,发现该错误(即没有取消装箱/ NPE)似乎仅在表达式是带有返回类型的类型变量的方法时发生。

有趣的是,我发现以下程序也在 Java
8中引发:

import java.util.*;

class Example {
    static void accept(Double d) {}

    public static void main(String[] args) {
        accept(false ? 1.0 : new HashMap<String, Double>().get("1"));
    }
}

这表明编译器的行为实际上是不同的,具体取决于将三元表达式分配给局部变量还是方法参数。

(本来我想使用重载来证明编译器为三元表达式提供的实际类型,但是鉴于上述差异,这似乎不太可能。我可能还没有想到另一种方法,虽然。)



 类似资料:
  • 问题内容: 为什么此代码有效? 为什么这会引发异常? 但是最奇怪的是,该代码也可以成功运行,没有任何异常: 看来Java的三元运算符会改变行为。有人可以解释为什么吗? 问题答案: 该行为在JLS- 条件运算符中 指定: 如果第二和第三个操作数中的一个是原始类型T的,并且其他的类型是施加装箱转换(§5.1.7)到T的结果,则 条件表达式的类型为T 。 强调我的。因此,在第二种情况下: 由于第三个操作

  • 条件(三元)运算符是 JavaScript 仅有的使用三个操作数的运算符。一个条件后面会跟一个问号(?),如果条件为 truthy ,则问号后面的表达式A将会执行;表达式A后面跟着一个冒号(:),如果条件为 falsy ,则冒号后面的表达式B将会执行。本运算符经常作为 if 语句的简捷形式来使用。(MDN) 三元运算符,也称条件运算符、三目运算符。 三元运算符可以代替简单的 if 语句。 1. 语

  • 问题内容: 我有时会从下面的行中得到。 添加括号后,就可以了。 请澄清我的行为。提前致谢。 问题答案: 永远不会为null,尽管有时是。 也就是说,等同于总是如此。

  • 问题内容: 是否可以更改此: …对三元运算符? 问题答案: 好吧,中的行为就像这样…… …另一种看待它的方式… 你的问题有点含糊,我们必须在这里假设。 如果(且仅当)声明了一个返回值(,等。)-现在看来似乎没有做到这一点通过你的代码-那么你可以做到这一点… 如果callFunction(…)不返回值,那么你将无法使用三元运算符!就那么简单。你将使用不需要的东西。 请发布更多代码以清除所有问题 尽管

  • 问题内容: 是否可以在Python中一行执行此操作? 我已经尝试过三元运算符: 但是我的IDE(MyEclipse)不喜欢它,没有一个。 问题答案: 是的,您可以这样做: 如果为假,则短路将开始,并且右侧将不被评估。如果为true,则将评估右侧并添加元素。 我只是指出,执行上述操作是完全非Python的,无论如何,最好编写此代码: 示范:

  • 问题内容: 有人可以解释为什么这样的代码吗? 产生以下编译器错误: 不兼容的条件操作数类型ArrayList和HashSet 由于我不明白的原因,以下内容解决了该问题 我正在使用Java 1.4。 问题答案: 这是1.4 中的 错误 ,并已根据bugreport 5080917 进行了修复。 评估 这是一个错误。 xxxxx @ xxxxx 2004-07-30