Почему выражение индекса массива Java оценивается перед проверкой, является ли выражение ссылки на массив нулевым?

Согласно JLS, оценка времени выполнения выражения доступа к массиву ведет себя следующим образом:

  1. Сначала оценивается выражение ссылки на массив. Если эта оценка завершается преждевременно, то доступ к массиву завершается преждевременно по той же причине, и выражение индекса не оценивается.
  2. В противном случае вычисляется индексное выражение. Если эта оценка завершается преждевременно, то доступ к массиву завершается преждевременно по той же причине.
  3. В противном случае, если значение выражения ссылки на массив равно нулю, генерируется исключение 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 выполняет проверку нулевого указателя. На самом деле код немного сложнее:

  1. вычислить адрес массива
  2. рассчитать индекс
  3. IALOAD

Таким образом, ответ таков: после загрузки адреса массива потребуется дополнительная проверка в ожидании доступа к массиву.

Поведение путем прямой реализации.

Ответ 4

Решение может быть частично основано на исполнении.

Чтобы знать, что index = 2 не требуется, нам нужно сначала оценить nada() а затем проверить, было ли оно нулевым. Затем мы ответим на результат этого условия и решим, следует ли оценивать выражение индекса массива.

Каждое совершенно правильное выражение индекса массива будет сделано медленнее одной дополнительной операцией, просто ради сохранения кода - кода, который все равно будет вызывать исключение - от ненужной оценки одного выражения.

Это оптимистичный подход, который работает лучше в большинстве случаев.