[Путешествие по основным принципам — познакомим вас с сущностью лямбда-выражений] | Перфокарта Java

Java задняя часть
[Путешествие по основным принципам — познакомим вас с сущностью лямбда-выражений] | Перфокарта Java

Эта статья участвует в «Месяц Java Theme - штамповка вопроса Java»,Ссылка на активность

тема

Познакомьте вас с сущностью лямбда-выражений

Точка знаний

Принцип лямбда

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

Лямбда-кейс

import java.util.function.Consumer;
public class Lambda {
	public static void main(String[] args) {
		Consumer<String> c = s -> System.out.println(s);
		c.accept("hello lambda!");
	}
}

Лямбда-выражения

Когда я только что увидел это выражение, я почувствовал, что метод обработки java принадлежит внутреннему анонимному классу.

public class Lambda {
	static {
		System.setProperty("jdk.internal.lambda.dumpProxyClasses", ".");
	}
	public static void main(String[] args) {
		Consumer<String> c = new Consumer<String>(){
			@Override
			public void accept(String s) {
				System.out.println(s);
			}
			};
		c.accept("hello lambda");
	}
}

Результат компиляции должен быть Lambda.class, Lambda$1.class Предполагаем, что поддержка динамического языка java не является изменением, а наш общий метод генерируется при финальной компиляции. Но результат не такой, просто генерируется Lambda.class

Декомпилируйте его, посмотрим, что там на самом деле?

javap -v -p Lambda.class 
  • Параметр -p будет отображать все методы без декомпиляции приватных методов по умолчанию.
public Lambda();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #21                 // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 3: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   LLambda;
  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=2, args_size=1
         0: invokedynamic #30,  0             // InvokeDynamic #0:accept:()Ljava/util/function/Consumer;
         5: astore_1
         6: aload_1
         7: ldc           #31                 // String hello lambda
         9: invokeinterface #33,  2           // InterfaceMethod java/util/function/Consumer.accept:(Ljava/lang/Object;)V
        14: return
      LineNumberTable:
        line 8: 0
        line 9: 6
        line 10: 14
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      15     0  args   [Ljava/lang/String;
            6       9     1     c   Ljava/util/function/Consumer;
      LocalVariableTypeTable:
        Start  Length  Slot  Name   Signature
            6       9     1     c   Ljava/util/function/Consumer<Ljava/lang/String;>;
 
  private static void lambda$0(java.lang.String);
    descriptor: (Ljava/lang/String;)V
    flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #46                 // Field java/lang/System.out:Ljava/io/PrintStream;
         3: aload_0
         4: invokevirtual #50                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         7: return
      LineNumberTable:
        line 8: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       8     0     s   Ljava/lang/String;
 }
SourceFile: "Lambda.java"
BootstrapMethods:
  0: #66 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;
    Method arguments:
      #67 (Ljava/lang/Object;)V
      #70 invokestatic Lambda.lambda$0:(Ljava/lang/String;)V
      #71 (Ljava/lang/String;)V
InnerClasses:
     public static final #77= #73 of #75; //Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles

сосредоточиться на методе

Invokedynamic
  • Четыре основных инструкции вызова функций в Java (invokevirtual, invokespecial, invokestatic, invokeinterface), как правило, символическая ссылка метода может быть сгенерирована при компиляции языка со статической типизацией.

  • Динамически напечатанные языки могут определять только тип приемника во время выполнения,Семантическое изменение четырех инструкций оказывает большое влияние на версию Java, поэтому в JSR 292 «Поддержка языков с динамической типизацией на платформе Java» добавлена ​​новая директива: Invokedynamic.

// InvokeDynamic #0:accept:()Ljava/util/function/Consumer;
0: invokedynamic #30,0
  • #30 означает константу #30, которая является следующим комментарием InvokeDynamic #0:accept:

()Ljava/util/function/Consumer;

  • 0 - это заполнитель, в настоящее время бесполезный
BootstrapMethods

Каждый экземпляр инструкции invokedynamic называется сайтом динамического вызова., сайт динамического вызова изначально не связан (несвязанный: указывает, что метод, который будет вызываться сайтом вызова, не указан), и сайт динамического вызова полагается на метод начальной загрузки для ссылки на конкретный метод.

Метод начальной загрузки генерируется компилятором,Когда JVM встречает инструкцию invokedynamic в первый раз во время выполнения, она вызывает метод начальной загрузки, чтобы связать имя (имя метода, сигнатура метода), заданное инструкцией invokedynamic, с конкретным кодом выполнения (целевым методом) и возвращаемым значением. значение метода начальной загрузки Постоянно определяет поведение сайта вызова.

CallSite

Тип возвращаемого значения метода загрузки — java.lang.invoke.CallSite, инструкция invokedynamic связана с CallSite, и все вызовы делегируются текущей цели (MethodHandle) CallSite.

InvokeDynamic #0 — это место, где BootstrapMethods представляет #0

  0: #66 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;
    Method arguments:
      #67 (Ljava/lang/Object;)V
      #70 invokestatic Lambda.lambda$0:(Ljava/lang/String;)V
      #71 (Ljava/lang/String;)V

Мы видим метод под названием LambdaMetaFactory.metafactory.

параметр:

  • LambdaMetafactory.metafactory(Lookup, String, MethodType, MethodType, MethodHandle, MethodType)Имеется шесть параметров, описанных по порядку следующим образом.

    1. MethodHandles.Lookup caller: Представляет права доступа контекста поиска и вызывающей стороны.При использовании инструкции invokedynamic JVM автоматически заполняет этот параметр.

    2. String invokedName : имя метода, который нужно реализовать.При использовании invokedynamic JVM автоматически заполняет его для нас (содержимое заполнения поступает из пула констант InvokeDynamic.NameAndType.Name), где JVM заполняет «apply» для us, то есть имя метода Consumer.accept.

    3. MethodType invokedType : Типы аргументов метода и возвращаемые значения, ожидаемые сайтом вызова (сигнатура метода).

      • При использовании инструкции invokedynamic JVM автоматически заполняет этот параметр (содержимое заполнения — из пула констант InvokeDynamic.NameAndType.Type), где параметр — String, тип возвращаемого значения — Consumer, а параметр целевого метода представляющим этот сайт вызова, является String, а затем после выполнения invokedynamic он вернет экземпляр Consumer.
    4. MethodType samMethodType :  Тип метода интерфейса, который реализует объект функции.Во время выполнения значением является (Object)Object, который является типом метода Consumer.accept (общая информация стирается)..

    #67 (Ljava/lang/Object;)V

    1. MethodHandle implMethod : Дескриптор прямого метода (DirectMethodHandle), описывающий конкретный метод реализации, который будет выполняться при вызове (включая соответствующую адаптацию параметров, адаптацию возвращаемого типа и добавление захваченных параметров перед параметрами вызова)..

      • Вот дескриптор метода #70 invokestatic Lambda.lambda$0:(Ljava/lang/String;)V.
    2. MethodType instanceiatedMethodType : тип метода после того, как метод функционального интерфейса заменяет универсальный тип конкретным типом, обычно таким же, как samMethodType, но универсальный тип отличается:

  • Например, метод функционального интерфейса определяется какvoid accept(T t)T — универсальный идентификатор, а тип метода в настоящее время — (Object)Void.

    • T был определен во время компиляции, а именно String заменяет T, тогда samMethodType является (Object) Void,

    • InstantiatedMethodype (строка) пустота.

4-й, 5-й и 6-й параметры берутся из файла класса. Например, три параметра после аргументов метода в байт-коде метода загрузки выше — это параметры, которые будут применяться к 4, 5 и 6.

  Method arguments:
      #67 (Ljava/lang/Object;)V
      #70 invokestatic Lambda.lambda$0:(Ljava/lang/String;)V
      #71 (Ljava/lang/String;)V
public static CallSite metafactory(MethodHandles.Lookup caller,
                                       String invokedName,
                                       MethodType invokedType,
                                       MethodType samMethodType,
                                       MethodHandle implMethod,
                                       MethodType instantiatedMethodType)
            throws LambdaConversionException {
        AbstractValidatingLambdaMetafactory mf;
        mf = new InnerClassLambdaMetafactory(caller, invokedType,
                                             invokedName, samMethodType,
                                             implMethod, instantiatedMethodType,
                                             false, EMPTY_CLASS_ARRAY, EMPTY_MT_ARRAY);
        mf.validateMetafactoryArgs();
        return mf.buildCallSite();
}

В функции buildCallSite

CallSite buildCallSite() throws LambdaConversionException {
        final Class<?> innerClass = spinInnerClass();

Функция spinInnerClass строит этот внутренний класс, то есть генерируется внутренний класс, такой как Lambda$$Lambda$1/716157500. Этот класс строится во время выполнения и не будет сохранен на диске.Если вы хотите увидеть этот построенный класс, вы можете установить параметры среды с помощью

System.setProperty("jdk.internal.lambda.dumpProxyClasses", ".");

Этот внутренний класс будет сгенерирован по указанному вами пути, текущему рабочему пути

статический класс

Java будет генерировать статический частный метод класса lambda$0 при компиляции выражения, в этом методе реализован блок методов system.out.println(s) в выражении;

private static void lambda$0(java.lang.String);
    descriptor: (Ljava/lang/String;)V
    flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #46                 // Field java/lang/System.out:Ljava/io/PrintStream;
         3: aload_0
         4: invokevirtual #50                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         7: return
      LineNumberTable:
        line 8: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       8     0     s   Ljava/lang/String;

Конечно, на предыдущем шаге, установивjdk.internal.lambda.dumpProxyClassesгенерируется в

Lambda$$Lambda$1.class
 public void accept(java.lang.Object);
    descriptor: (Ljava/lang/Object;)V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=2, args_size=2
         0: aload_1
         1: checkcast     #15                 // class java/lang/String
         4: invokestatic  #21                 // Method Lambda.lambda$0:(Ljava/lang/String;)V
         7: return
    RuntimeVisibleAnnotations:
      0: #13()

Вызывается статическая функция Lambda.lambda$0, которая является функциональным блоком в выражении

В заключении

Это завершает реализацию лямбда-выражения,

  1. С помощью инструкции invokedynamic среда выполнения вызывает LambdaMetafactory.metafactory для динамического создания внутреннего класса, реализующего интерфейс.,

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