为什么在检查数组引用表达式是否为null之前先评估Java数组索引表达式?

时间:2019-03-14 11:00:57

标签: java language-lawyer

根据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?

4 个答案:

答案 0 :(得分:37)

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

  

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

为了评估表达式,在数组访问表达式本身之前先评估两个子表达式。

在评估了两个子表达式之后

nada()[index = 2]++;

成为

null[2]++;

现在仅对表达式求值并抛出NullPointerException

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

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

firstMethod().secondMethod(i = 2);

首先,您评估firstMethod()i = 2,直到NullPointerException评估为firstMethod()后,您才抛出null

答案 1 :(得分:14)

这是因为在生成的字节码中没有显式的null检查。

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

答案 2 :(得分:8)

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

ALOAD array_address
ILOAD index
IALOAD array_element_retrieval

IALOAD执行空指针检查。实际上,代码要复杂一些:

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

所以答案是:预期要访问数组时,在加载数组地址后需要进行额外检查操作

通过直接实现的行为。

答案 3 :(得分:8)

该决定可能部分取决于性能。

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

通过一个额外的操作,每个完全有效的数组索引表达式都会变慢,只是为了节省代码-总是会抛出异常的代码-不必评估一个表达式。

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