Почему getClass () вызывается, когда мы создаем объект для внутреннего класса? - PullRequest
6 голосов
/ 04 июля 2019

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

Я попытался понять, что для проверки нуля не требуется, после JDK 8 его заменила статическая функция с именем requiredNoNull.

Код:

class Outer{
      class Inner{
      }
      public static void main(String args[]){
            Outer.Inner obj = new Outer().new Inner();
      } 
}

ByteCode:

 public static void main(java.lang.String[]);
    Code:
       0: new           #2                  // class Outer$Inner
       3: dup
       4: new           #3                  // class Outer
       7: dup
       8: invokespecial #4                  // Method "<init>":()V
      11: dup
      12: invokevirtual #5                  // Method java/lang/Object.getClass:()Ljava/lang/Class;
      15: pop
      16: invokespecial #6                  // Method Outer$Inner."<init>":(LOuter;)V
      19: astore_1

Ответы [ 3 ]

3 голосов
/ 04 июля 2019

Это скрытая проверка , ни больше, ни меньше. Хотя, в данном конкретном случае это на самом деле не нужно, и будущее javac немного его оптимизирует - посмотрите на пример ниже.

Может быть, это лучше объяснит проблему (используя java-12, где этот getClass хак был заменен на Objects::requireNonNull):

public class Outer {

    class Inner {

    }

    public void left() {
        Outer.Inner inner = new Outer().new Inner();
    }

    public void right(Outer outer) {
        Outer.Inner inner = outer.new Inner();
    }
}

left метод скомпилируется во что-то (вы можете посмотреть байт-код самостоятельно), которое не использует Objects::requireNonNull, так как создание Outer происходит на месте, и компилятор может обязательно скажите, что new Outer() экземпляр не является null.

С другой стороны, вы передаете Outer в качестве параметра, компилятор не может доказать, что переданный экземпляр не является нулевым, поэтому Objects::requireNonNull будет присутствовать в байтовом коде ,

2 голосов
/ 04 июля 2019

Насколько я понимаю, @ ответ Евгения абсолютно правильный. Я решил добавить объяснение простыми словами. Надеюсь, это кому-нибудь поможет.

Ответ: Вызовы Object.getClass использовались компилятором в JDK8 для генерации исключений NullPointerException, где это необходимо. В вашем примере эта проверка не нужна, так как new Outer() не может быть нулем, но компилятор не был достаточно умен, чтобы определить это.

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

Пояснение:

Рассмотрим код, подобный следующему:

class Outer{
      class Inner{
      }
      public static void main(String args[]){
            Outer.Inner obj = ((Outer) null).new Inner();
      } 
} 

Этот код генерирует исключение NullPointerException, как и должно быть.

Проблема в том, что NPE логичен только с точки зрения Java. Конструкторы не существуют на уровне байтового кода. Компилятор генерирует байт-код, более или менее эквивалентный следующему псевдокоду:

class Outer {
    public static void main(String[] args) {
         Outer tmp = (Outer) null;
         Outer.Inner obj = new; //object created
         tmp."<init>"(tmp);
    }
}
class Outer$Inner {
    //generated field
    private final Outer outer;
    //generated initializer
    void "<init>"(Outer outer) {
         this.outer = outer;
    }    
}

Как видите, конструктор был заменен методом. И метод сам по себе не будет проверять свой аргумент на null и, следовательно, не будет генерировать исключение.

По этой причине компилятор должен добавить дополнительную нулевую проверку для генерации NullPointerException. До Java 8 быстрым и грязным способом для достижения этой цели было отправлять вызов на getClass:

Outer tmp = (Outer) null;
tmp.getClass(); //generates an NPE

Как вы можете проверить, что это, действительно, причина:

  1. Скомпилируйте класс Outer выше, используя JDK 8.
  2. Запустите его, он должен бросить NPE.
  3. Удалите вызов на Object.getClass из Outer.class с помощью любого редактора байт-кода (например, JBE ).
  4. Запустите программу еще раз, она должна успешно завершиться.
0 голосов
/ 04 июля 2019

Поскольку определение класса Inner присутствует во внешнем классе, JVM необходимо сначала загрузить внешний класс.

...