Почему javac вставляет Objects.requireNonNull(this) для конечных полей?

Рассмотрим следующий класс:

class Temp {
    private final int field = 5;

    int sum() {
        return 1 + this.field;
    }
}

Затем я компилирую и декомпилирую класс:

> javac --version
javac 11.0.5

> javac Temp.java

> javap -v Temp.class
  ...
  int sum();
    descriptor: ()I
    flags: (0x0000)
    Code:
      stack=2, locals=1, args_size=1
         0: iconst_1
         1: aload_0
         2: invokestatic  #3   // Method java/util/Objects.requireNonNull:(Ljava/lang/Object;)Ljava/lang/Object;
         5: pop
         6: iconst_5
         7: iadd
         8: ireturn

Проще говоря, javac компилирует sum() в это:

int sum() {
    final int n = 1;
    Objects.requireNonNull(this); // <---
    return n + 5;
}

Что здесь делает Objects.requireNonNull(this)? В чем смысл? Это как-то связано с доступностью?

Компилятор Java 8 аналогичен. Он вставляет this.getClass() вместо Objects.requireNonNull(this):

int sum() {
    final int n = 1;
    this.getClass(); // <---
    return n + 5;
}

Я также пытался скомпилировать его с помощью Eclipse. Он не вставляет requireNonNull:

int sum() {
    return 1 + 5;
}

Итак, это поведение, специфичное для javac.


person ZhekaKozlov    schedule 12.06.2020    source источник
comment
Кажется, он не понимает, что это ненужно для this по сравнению с другими выражениями.   -  person Holger    schedule 12.06.2020
comment
хороший улов(+1). чтобы добавить к этому, такое же поведение можно наблюдать и с javac 15-ea. дальнейшее наблюдение, удаление final работает нормально.   -  person Naman    schedule 12.06.2020
comment
пытаясь вписаться в комментарий, int sum();... Code: stack=2, locals=1, args_size=1 0: iconst_1 1: aload_0 2: invokestatic #13 // Method java/util/Objects.requireNonNull:(Ljava/lang/Object;)Ljava/lang/Object; 5: pop 6: iconst_5 7: iadd 8: ireturn по сравнению с удалением окончательного ключевого слова, разрешающегося в int sum();... Code: stack=2, locals=1, args_size=1 0: iconst_1 1: aload_0 2: getfield #7 // Field field:I 5: iadd 6: ireturn   -  person Naman    schedule 12.06.2020


Ответы (1)


Поскольку поле не только final, но и является константой времени компиляции, оно не будет доступно при чтении, но чтение заменяется самим постоянным значением, инструкцией iconst_5 в вашем случае.

Но поведение выдачи NullPointerException при разыменовании null, которое подразумевается при использовании инструкции getfield, должно быть сохранено¹. Поэтому, когда вы меняете метод на

int sumA() {
  Temp t = this;
  return 1 + t.field;
}

Eclipse также вставит явную нулевую проверку.

Итак, что мы видим здесь, так это то, что javac не может распознать, что в этом конкретном случае, когда ссылка this, ненулевое свойство гарантируется JVM, и, следовательно, явная проверка нулевого значения не требуется.

¹ см. JLS §15.11.1. Доступ к полю с помощью основного:

  • Если поле не static:

    • The Primary expression is evaluated. If evaluation of the Primary expression completes abruptly, the field access expression completes abruptly for the same reason.
    • Если значение Primary равно null, выдается NullPointerException.
    • Если поле представляет собой непустое значение final, результатом будет значение именованного поля-члена типа T, найденное в объекте, на который ссылается значение Primary.

person Holger    schedule 12.06.2020
comment
Хорошая точка зрения. Еще проще продемонстрировать так: Temp t = null; return 1 + t.field. Objects.requireNonNull(t), иначе он вернул бы 6 без NPE. - person ZhekaKozlov; 12.06.2020
comment
Разве это не тот факт, что другой класс может использовать отражение, чтобы в любой момент времени установить для поля значение null? (Final — это языковая конструкция, а не ограничение времени выполнения). - person MTilsted; 13.06.2020
comment
@MTilsted: Нет, потому что в этом случае само поле представляет собой примитив int, который не может быть нулевым. - person Ilmari Karonen; 13.06.2020
comment
@MTilsted изменение конечных полей с отражением может привести к неопределенному поведению. Потенциально не затрагивая встроенные значения, является одним из них. - person k5_; 13.06.2020
comment
@MTilsted проверка касается не значения поля. Речь идет о ссылке на владельца поля, то есть this. Ссылку нельзя изменить через Reflection и нельзя быть null, потому что невозможно войти в метод экземпляра с null ссылкой на приемник; инструкция вызова уже потерпела бы неудачу. - person Holger; 15.06.2020
comment
Существует политика, согласно которой javac не выполняет оптимизацию. Раньше это было, но усложняло и, я думаю, мешало HotSpot. Я думаю, что было изменение JVM, которое сделало getClass нежелательным (не помню что), но это и Objects.requireNonNull являются встроенными компонентами HotSpot. - person Tom Hawtin - tackline; 12.09.2020
comment
@TomHawtin-tackline переход от использования getClass к requireNonNull рассмотрен в этих вопросах и ответах. - person Holger; 13.09.2020