Java 8 동적 유형 언어 Lambda 표현식 실현 원리 해석

10143 단어 java8lambda표현식
Java8은 동적 언어를 지원하고 멋진 Lambda 표현식을 보았으며 정적 유형 언어로 자처해 온 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!"); 
  } 
} 
방금 이 표현식을 보았는데, 자바의 처리 방식은 내부 익명류에 속하는 방식이라고 느꼈다

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"); 
  } 
} 
번역한 결과는 람다일 것이다.class , Lambda$1.class는 동적 언어인 자바 환탕을 지원하고 약을 바꾸지 않으며 마지막으로 번역할 때 우리가 흔히 볼 수 있는 방식을 생성할 것이라고 추측합니다.
그러나 결과는 그렇지 않았다. 단지 람다가 생겼을 뿐이다.class
반번역해라, 진상이 무엇인지 볼까?

javap -v -p Lambda.class 
주의-p 이 매개 변수 -p 매개 변수는 모든 방법을 표시합니다. 기본값이 없으면private 방법을 역컴파일하지 않습니다.

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 
여기서 우리는 우리가 흔히 볼 수 있는 자바와 그다지 다른 몇 가지 부분을 발견했다. 상량 정의가 너무 많아서 문장에 붙이지 않았다
1. Invokedynamic 명령
자바의 호출 함수의 4대 지령(invokevirtual, invokespecial, invokestatic, invokeinterface)은 일반적인 방법의 기호 인용은 정적 형식 언어를 컴파일할 때 발생할 수 있지만 동적 형식 언어는 운행 기간에만 수신자의 유형을 확정할 수 있고 4대 지령의 의미를 바꾸는 것은 자바의 버전에 큰 영향을 미친다.그래서 JSR 292에 새로운 지령을 추가했습니다.
Invokedynamic
0: invokedynamic#30,  0            //InvokeDynamic#0:accept:()Ljava/util/function/Consumer; 
#30은 상량 #30, 즉 뒤에 있는 주석인 InvokeDynamic#0:accept: () Ljava/util/function/Consumer;
0은 차지 기호이며 현재 쓸모가 없습니다.
2. BootstrapMethods
모든 invokedynamic 명령의 실례는 동적 호출점(dynamiccallsite)이라고 하는데 동적 호출점은 처음에 연결되지 않은 상태(unlinked: 이 호출점이 호출할 방법을 지정하지 않았음을 나타냄)이고 동적 호출점은 안내 방법에 의존하여 구체적인 방법으로 연결된다.부트 방법은 컴파일러에 의해 생성됩니다. 실행 기간에 JVM이 invokedynamic 명령을 처음 만났을 때 부트 방법을 호출하여 invokedynamic 명령이 지정한 이름(방법 이름, 방법 서명)과 구체적인 실행 코드(대상 방법)를 연결합니다. 부트 방법의 반환 값은 호출 지점의 행위를 영구적으로 결정합니다.안내 방법의 반환값 형식은java입니다.lang.invoke.CallSite, invokedynamic 명령이 CallSite와 연결되어 모든 호출을 CallSite의 현재 target(MethodHandle)에 의뢰합니다
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 
우리는 람다메타팩토리를 호출하는 것을 보았다.metafactory 방법
매개변수:
LambdaMetafactory.metafactory(Lookup, String, MethodType, MethodType, MethodHandle, MethodType)에는 다음과 같은 6개의 매개변수가 있습니다.
1. MethodHandles.Lookup caller: 컨텍스트와 호출자에 대한 액세스 권한을 찾습니다. invokedynamic 명령을 사용하면 JVM이 자동으로 이 인자를 채웁니다.
2. String invokedName: 구현할 방법의 이름, invokedynamic을 사용할 때 JVM이 자동으로 채워줍니다(상량 풀 InvokeDynamic.NameAndType.Name). 여기서 JVM은'apply', 즉 Consumer로 채워줍니다.accept 방법명.
3. MethodType invokedType: 호출점에서 원하는 방법 매개 변수의 유형과 반환 값의 유형(방법signature).invokedynamic 명령을 사용할 때 JVM은 자동으로 이 매개 변수를 채웁니다. (충전 내용은 상수 탱크 InvokeDynamic.NameAndType.Type에서 온 것입니다.) 여기서 매개 변수는 String이고 반환 값 형식은 Consumer입니다. 이 호출점의 목표 방법을 나타내는 매개 변수는 String입니다. 그리고 invokedynamic가 실행된 후에 Consumer 실례로 되돌아옵니다.
4. MethodType samMethodType: 함수 객체가 구현할 인터페이스 메소드 유형입니다. 여기서 실행할 때 값은 (Object)Object 즉 Consumer입니다.accept 방법의 유형(범용 정보 삭제).#67 (Ljava/lang/Object;)V
5. MethodHandle implMethod: 직접 방법 핸들(Direct MethodHandle)으로 호출할 때 실행될 구체적인 실현 방법(적당한 매개 변수 적합, 반환 유형 적합, 호출 매개 변수 앞에 포획된 매개 변수를 추가)을 설명합니다. 여기서 #70 invokestatic Lambda입니다.lambda$0:(Ljava/lang/String;)V 방법의 방법 핸들.
6. MethodType instantiatedMethodType: 함수 인터페이스 방법은 범주를 구체적인 유형으로 대체한 후의 방법 유형으로 보통samMethodType과 마찬가지로 서로 다른 상황은 범주형이다.
예를 들어 함수 인터페이스 방법은void accept(T t) T를 범용 표지로 정의하고 이때 방법 유형은 (Object)Void이다. 컴파일할 때 T는 String에서 교체된다. 이때samMethodType은 (Object)Void이고 instantiatedMethodType은 (String)Void이다.
4, 5, 6 세 개의 매개 변수는class 파일에서 왔습니다.위의 안내 방법 바이트 코드에서 Methodarguments 뒤에 있는 세 가지 파라미터는 4, 5, 6에 응용될 파라미터입니다.

Method arguments: 
  #67 (Ljava/lang/Object;)V 
  #70 invokestatic Lambda.lambda$0:(Ljava/lang/String;)V 
  #71 (Ljava/lang/String;)V 
metafactory 방법의 실현 코드를 보겠습니다.

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", "."); 
당신이 지정한 경로에 있을 것입니다.현재 실행 경로에서 이 내부 클래스 생성
3. 정적 클래스
Java는 표현식을 컴파일할 때 lambda$0 정적 개인 클래스를 생성합니다. 이 클래스에서 표현식의 방법 블록 시스템을 실현했습니다.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$0 정적 함수, 즉 표현식의 함수 블록
총결산
이렇게 하면 Lambda 표현식을 실현하고 invokedynamic 명령을 사용하여 실행할 때 Lambda Metafactory를 호출합니다.metafactory 동적 생성 내부 클래스는 인터페이스를 실현했다. 내부 클래스의 호출 방법 블록은 동적 생성이 아니라 원class에서 정적 방법을 컴파일하여 생성했다. 내부 클래스는 이 정적 방법만 호출해야 한다.
위에서 말한 것은 편집자가 여러분께 소개한 Java8 동적 유형 언어인 Lambda 표현식의 실현 원리 해석입니다. 여러분께 도움이 되었으면 합니다. 만약에 궁금한 것이 있으면 저에게 메시지를 남겨 주십시오. 편집자는 제때에 여러분에게 회답할 것입니다.여기에서도 저희 사이트에 대한 지지에 감사드립니다!

좋은 웹페이지 즐겨찾기