根据JLS,对数组访问表达式的运行时评估的行为如下:
因此该代码将打印: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?
答案 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执行空指针检查。实际上,代码要复杂一些:
所以答案是:预期要访问数组时,在加载数组地址后需要进行额外检查操作。
通过直接实现的行为。
答案 3 :(得分:8)
该决定可能部分取决于性能。
为了知道不需要index = 2
,我们必须首先评估nada()
,然后检查它是否为空。然后,我们将基于此条件的结果进行分支,并决定是否评估数组索引表达式。
通过一个额外的操作,每个完全有效的数组索引表达式都会变慢,只是为了节省代码-总是会抛出异常的代码-不必评估一个表达式。
这是一种乐观的方法,在大多数情况下效果更好。