테코톡 - 람다 준비

Posted by yunki kim on March 12, 2022

Functional Interface in Java:https://www.geeksforgeeks.org/functional-interfaces-java/

 

Functional Interfaces in Java - GeeksforGeeks

A Computer Science portal for geeks. It contains well written, well thought and well explained computer science and programming articles, quizzes and practice/competitive programming/company interview Questions.

www.geeksforgeeks.org

  함수형 인터페이스는 오직 하나의 추상 메서드만을 가져야 하지만 여러 개의 default method, static method를 가질 수 있다.

@FunctionalInterface 어노테이션은 함수형 인터페이스가 오직 하나의 추상 메서드를 가질 수 있음을 보장한다.

 

Internal working of the functional interface and lambda expression:https://amanagrawal9999.medium.com/internal-working-of-functional-interface-and-lambda-expression-d6a19e5d2f46

 

Internal working of the functional interface and lambda expression

This blog will help you to understand how JVM handles functional interface and lambda expression.

amanagrawal9999.medium.com

JVM이 람다를 처리하는 방식은 다음과 같다:

  컴파일러가 람다를 바이트 코드로 전환하면 해당 람다가 존재하는 클래스의 static private 또는 private method로 람다를 변환한다.

  static private과 private의 결정 기준은 람다가 인스턴스 멤버를 사용하는지 여부에 달렸다. 

람다의 바이트 코드 예시

  이렇게 변환된 메서드는 lambda$x$y 라는 이름을 갖는다. 여기서 x는 해당 람다를 가지고 있는 메서드의 이름이고 ysms 0부터 시작하는 순차적인 숫자다. 

  자바 8 이전에는 인터페이스의 구현체를 만들기 위해선 구현 클래스를 정의하던가 익명 클래스를 사용했어야 했다. 이 방식들은 컴파일 타임에 별도의 클래스 파일을 만든다.

  익명 클래스를 사용하는 경우:

Filter class의 main 메서드

    이를 컴파일 하면 다음과 같이 두 개의 클래스 파일이 생긴다.

  여기서 Filter.class는 Filter class에 대한 클래스 파일이고 Filter$1.class는 Filter 클래스 내부에서 생성한 익명 클래스에 대한 클래스 파일이다.

Filter$1.class

  구현체를 정의해 사용하면 다음과 같이 클래스 파일이 정의된다.

StudentPredicate의 구현체 GpaPredicate

  람다의 경우 자바 7에서 도입된 JVM invokedynamic을 사용한다. invokedynamic은 클래스 생성을 런타임 까지 지연시킨다. 컴파일 타임 때, 람다는 invokedynamic으로 대체된다.

  아래의 코드를

  바이트 코드로 보면 람다 부분이 invokedynamic으로 대체 된것을 볼 수 있다.

   동적타입 언어의 경우, 런타임 까지는 어떤 메서드가 만들어 지는지 알 수 없다. 이에 대응하기 위해 call site를 런타임에 동적으로 메서드, 연산을 찾거나 구성하고 호출하는 데 필요한 정보, 논리를 나타내는 객체라 가정할 수 있다. 자바에선 이것이 java.lang.invoke.CallSite로 표현된다.

  이 callSite는 method handle에 컨테이너 역할을 한다. method handle은 메서드를 찾고, 적용하고, 호출하기 위한 low-level mechanism이다.(Method handles in java: https://www.baeldung.com/java-method-handles)

method handle의 매커니즘은 다음과 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class MethodHandleTmp {
    public static void staticMethod(String b) {
        System.out.println(b);
    }
 
    public static void main(String[] args) throws Throwable {
        // loop up object(method handle을 생성하기 위한 팩토리)를 반환한다.
        MethodHandles.Lookup lookup = MethodHandles.lookup();
        // static method에 대한 method handle을 만든다.
        // findStatic의 파라미터는 다음과 같다
        // (메서드가 존재하는 클래스, 메서드 이름, 메서드 타입(반환값, 파라미터 타입))
        MethodHandle staticMethod = lookup.findStatic(MethodHandleTmp.class"staticMethod",
            MethodType.methodType(void.classString.class));
        // method handle을 호출한다.
        staticMethod.invoke("hello"); // "hello" 출력
    }
}
 
cs

  callsite를 호출하기 위해서 모든 invokedynamic 명령은 LambdaMetaFactory 클래스에 위임된다. 이 팩토리는 함수 인터페이스를 구현하는 클래스를 만들고 람다가 존재하는 클래스에 private method로 저장되는 람다 메서드를 호출하는 역할을 한다. 이 과정을 개략적으로 실제 코딩 해보면 다음과 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class LambdaTest {
 
    private static CallSite callSite;
 
    public static void main(String[] args) throws Throwable {
        if (callSite == null) {
            callSite = boostrapLambda(MethodHandles.lookup(),
                "lambda$main$0", MethodType.methodType(void.class));
        }
        Runnable runnable = (Runnable) callSite.getTarget().invokeExact();
        Thread thread = new Thread(runnable);
        thread.start();
    }
 
    private static void lambda$main$0() {
        System.out.println("lambda");
    }
 
    private static CallSite boostrapLambda(MethodHandles.Lookup lookup, String name, MethodType type) throws
            NoSuchMethodException,
            IllegalAccessException, LambdaConversionException {
 
        MethodHandle lambdaImplementation = lookup.findStatic(lookup.lookupClass(), name, type);
        return LambdaMetafactory.metafactory(lookup,
            "run",
            MethodType.methodType(Runnable.class),
            MethodType.methodType(void.class),
            lambdaImplementation,
            type);
    }
}
cs