Подтвердить что ты не робот

В Java Lambda почему getClass() вызывает захваченную переменную

Если вы посмотрите на байтовый код для

Consumer<String> println = System.out::println;

код байта, генерируемый Java 8, обновляется 121,

GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
DUP
INVOKEVIRTUAL java/lang/Object.getClass ()Ljava/lang/Class;
POP
INVOKEDYNAMIC accept(Ljava/io/PrintStream;)Ljava/util/function/Consumer; [
  // handle kind 0x6 : INVOKESTATIC
  java/lang/invoke/LambdaMetafactory.metafactory(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
  // arguments:
  (Ljava/lang/Object;)V, 
  // handle kind 0x5 : INVOKEVIRTUAL
  java/io/PrintStream.println(Ljava/lang/String;)V, 
  (Ljava/lang/String;)V
]
ASTORE 1

Метод getClass() вызывается в System.out, и результат игнорируется.

Является ли это косвенной нулевой ссылкой?

Конечно, если вы запустите

PrintStream out = null;
Consumer<String> println = out::println;

Это вызывает исключение NullPointerException.

4b9b3361

Ответ 1

Да, вызов getClass() стал каноническим "тестом для null", поскольку getClass(), как ожидается, будет дешевой внутренней операцией, и, я полагаю, HotSpot может обнаруживать этот шаблон и уменьшать если операция getClass() не используется.

Другим примером является создание внутреннего экземпляра класса с внешним экземпляром, который не является this:

public class ImplicitNullChecks {
    class Inner {}
    void createInner(ImplicitNullChecks obj) {
        obj.new Inner();
    }

    void lambda(Object o) {
        Supplier<String> s=o::toString;
    }
}

компилируется в

Compiled from "ImplicitNullChecks.java"
public class bytecodetests.ImplicitNullChecks {
  public bytecodetests.ImplicitNullChecks();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  void createInner(bytecodetests.ImplicitNullChecks);
    Code:
       0: new           #23                 // class bytecodetests/ImplicitNullChecks$Inner
       3: dup
       4: aload_1
       5: dup
       6: invokevirtual #24                 // Method java/lang/Object.getClass:()Ljava/lang/Class;
       9: pop
      10: invokespecial #25                 // Method bytecodetests/ImplicitNullChecks$Inner."<init>":(Lbytecodetests/ImplicitNullChecks;)V
      13: pop
      14: return

  void lambda(java.lang.Object);
    Code:
       0: aload_1
       1: dup
       2: invokevirtual #24                 // Method java/lang/Object.getClass:()Ljava/lang/Class;
       5: pop
       6: invokedynamic #26,  0             // InvokeDynamic #0:get:(Ljava/lang/Object;)Ljava/util/function/Supplier;
      11: astore_2
      12: return
}

См. также JDK-8073550:

Несколько мест в нашей библиотеке классов используют странный трюк использования object.getClass() для проверки недействительности. Хотя это кажется разумным шагом, это фактически путает людей, полагая, что это одобренный практика нулевой проверки.

С JDK 7 у нас есть Objects.requireNonNull, которые обеспечивают правильную нулевую проверку и объявляют намеренно.

Это может быть спорным, нужно ли это относится к языку программирования внутренним проверкам, а также, как использование Objects.requireNonNull для этой цели будет создать зависимость от класса за пределами java.lang пакета не виден в исходном коде. И в этом конкретном случае трюк доступен только тем, кто смотрит на байтовый код. Но было решено изменить поведение с Java 9.

Вот как jdk1.9.0b160 компилирует один и тот же тестовый класс:

Compiled from "ImplicitNullChecks.java"
public class bytecodetests.ImplicitNullChecks {
  public bytecodetests.ImplicitNullChecks();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  void createInner(bytecodetests.ImplicitNullChecks);
    Code:
       0: new           #26                 // class bytecodetests/ImplicitNullChecks$Inner
       3: dup
       4: aload_1
       5: dup
       6: invokestatic  #27                 // Method java/util/Objects.requireNonNull:(Ljava/lang/Object;)Ljava/lang/Object;
       9: pop
      10: invokespecial #28                 // Method bytecodetests/ImplicitNullChecks$Inner."<init>":(Lbytecodetests/ImplicitNullChecks;)V
      13: pop
      14: return

  void lambda(java.lang.Object);
    Code:
       0: aload_1
       1: dup
       2: invokestatic  #27                 // Method java/util/Objects.requireNonNull:(Ljava/lang/Object;)Ljava/lang/Object;
       5: pop
       6: invokedynamic #29,  0             // InvokeDynamic #0:get:(Ljava/lang/Object;)Ljava/util/function/Supplier;
      11: astore_2
      12: return
}