Почему выражение индекса массива Java оценивается перед проверкой, является ли выражение ссылки на массив нулевым?
Согласно JLS, оценка времени выполнения выражения доступа к массиву ведет себя следующим образом:
- Сначала оценивается выражение ссылки на массив. Если эта оценка завершается преждевременно, то доступ к массиву завершается преждевременно по той же причине, и выражение индекса не оценивается.
- В противном случае вычисляется индексное выражение. Если эта оценка завершается преждевременно, то доступ к массиву завершается преждевременно по той же причине.
- В противном случае, если значение выражения ссылки на массив равно нулю, генерируется исключение 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
а не просто выбросить исключение NullPointerException после того, как ссылка на массив будет оценена как нулевая? Или другими словами - почему порядок 1,2,3, а не 1,3,2?
Ответы
Ответ 1
Выражение доступа к массиву имеет два подвыражения:
Выражение доступа к массиву содержит два подвыражения: выражение ссылки на массив (перед левой скобкой) и индексное выражение (в скобках).
Два подвыражения оцениваются перед самим выражением доступа к массиву, чтобы оценить выражение.
После оценки двух подвыражений
nada()[index = 2]++;
становится
null[2]++;
Только теперь выражение оценивается и NullPointerException
.
Это согласуется с оценкой большинства выражений в Java (единственные встречные примеры, о которых я могу подумать, это операторы короткого замыкания, такие как && и ||).
Например, если вы делаете следующий вызов метода:
firstMethod().secondMethod(i = 2);
Во- первых вы оцениваете firstMethod()
и i = 2
, и только потом вы бросаете NullPointerException
если firstMethod()
оценивается в null
.
Ответ 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
Ответ 3
Основные операции с байтовым кодом (для int[]
)
ALOAD array_address
ILOAD index
IALOAD array_element_retrieval
IALOAD выполняет проверку нулевого указателя. На самом деле код немного сложнее:
- вычислить адрес массива
- рассчитать индекс
- IALOAD
Таким образом, ответ таков: после загрузки адреса массива потребуется дополнительная проверка в ожидании доступа к массиву.
Поведение путем прямой реализации.
Ответ 4
Решение может быть частично основано на исполнении.
Чтобы знать, что index = 2
не требуется, нам нужно сначала оценить nada()
а затем проверить, было ли оно нулевым. Затем мы ответим на результат этого условия и решим, следует ли оценивать выражение индекса массива.
Каждое совершенно правильное выражение индекса массива будет сделано медленнее одной дополнительной операцией, просто ради сохранения кода - кода, который все равно будет вызывать исключение - от ненужной оценки одного выражения.
Это оптимистичный подход, который работает лучше в большинстве случаев.