【问题标题】:Why is a Java array index expression evaluated before checking if the array reference expression is null?为什么在检查数组引用表达式是否为空之前评估 Java 数组索引表达式?
【发布时间】:2019-08-05 05:54:43
【问题描述】:

根据 JLS,数组访问表达式的运行时求值行为如下:

  1. 首先,计算数组引用表达式。如果这 评估突然完成,然后数组访问完成 突然出于同样的原因,索引表达式不是 评估。
  2. 否则,将评估索引表达式。如果这 评估突然完成,然后数组访问完成 突然出于同样的原因。
  3. 否则,如果数组的值 引用表达式为 null,则抛出 NullPointerException。

所以这段代码会打印:java.lang.NullPointerException, index=2

class Test3 {
    public static void main(String[] args) {
        int index = 1;
        try {
            nada()[index = 2]++;
        } catch (Exception e) {
            System.out.println(e + ", index=" + index);
        }
    }

    static int[] nada() {
        return null;
    }
}

问题是:出于什么原因,我们需要首先评估 index = 2 表达式,而不是在数组引用被评估为 null 时抛出 NullPointerException?或者换句话说 - 为什么顺序是 1,2,3 而不是 1,3,2?

【问题讨论】:

  • 首先你必须初始化一个数组,其次等号的优先级很高,这就是它给定空指针异常的原因。
  • 问为什么 JLS 是这样写的,除非它可能来自 Java 语言的设计者之一,否则不会给你任何好的答案。
  • 他们必须做出选择,而这两种选择都会产生“意想不到的”场景。 (即行为方式不是很直观的场景)。所选择的选项似乎更符合 Java 其他部分中表达式评估的工作方式。
  • @fantaghirocco 我没有询问java如何评估数组索引表达式,它在问题中有所描述。我只是想知道这种行为背后的原因。

标签: java language-lawyer


【解决方案1】:

一个数组访问表达式有两个子表达式:

数组访问表达式包含两个子表达式,数组引用表达式(左括号之前)和索引表达式(括号内)。

为了对表达式求值,这两个子表达式在数组访问表达式本身之前求值。

计算两个子表达式后

nada()[index = 2]++;

变成

null[2]++;

现在只计算表达式并抛出NullPointerException

这与 Java 中大多数表达式的求值一致(我能想到的唯一反例是短路运算符,例如 && 和 ||)。

例如,如果您进行以下方法调用:

firstMethod().secondMethod(i = 2);

首先您评估firstMethod()i = 2,然后如果firstMethod() 评估为null,则仅在稍后您抛出NullPointerException

【讨论】:

    【解决方案2】:

    这是因为在生成的字节码中没有明确的空检查。

    nada()[index = 2]++;
    

    被翻译成如下字节码:

    // evaluate the array reference expression
      INVOKESTATIC Test3.nada ()[I
    // evaluate the index expression
      ICONST_2
      DUP
      ISTORE 1
    // access the array
    // if the array reference expression was null, the IALOAD operation will throw a null pointer exception
      DUP2
      IALOAD
      ICONST_1
      IADD
      IASTORE
    

    【讨论】:

    • 这是交换因果关系。如果规范说null-tests 必须在索引评估之前发生,则有明确的null 检查。
    【解决方案3】:

    基本的字节码操作是(对于int[]

    ALOAD array_address
    ILOAD index
    IALOAD array_element_retrieval
    

    IALOAD 执行空指针检查。实际上代码更复杂一点:

    1. 计算数组地址
    2. 计算索引
    3. 加载

    所以答案是:在加载数组地址后,它需要一个额外的检查操作,以预期数组访问。

    直接实现的行为。

    【讨论】:

      【解决方案4】:

      该决定可能部分取决于绩效。

      为了知道index = 2 不是必需的,我们必须首先评估nada(),然后检查它是否为空。然后我们将根据这个条件的结果进行分支,并决定是否评估数组索引表达式。

      每一个完全有效的数组索引表达式都会因一个额外的操作而变慢,只是为了节省代码 - 无论如何都会引发异常的代码 - 避免不必要地评估一个表达式。

      这是一种乐观的方法,在大多数情况下效果更好。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-08-08
        • 1970-01-01
        • 2017-05-24
        • 1970-01-01
        • 1970-01-01
        • 2012-05-27
        相关资源
        最近更新 更多