phantasmicmeans 기술 블로그

12. Kotlin - Extension Function VS Function literal with receiver 본문

Programming/Kotlin

12. Kotlin - Extension Function VS Function literal with receiver

phantasmicmeans 2021. 1. 22. 13:02

코틀린의 가장 큰 장점중에 하나인 Extension Function
구글링해보면 이 주제에 관련된 자료는 무궁무진하고.. 솔직히 내용이 쉬워서 아래 자료를 보고 이해하면 될 것 같다.

간단하게 말해서 extension은 기존 라이브러리, SDK 등 우리가 수정할 수 없는 클래스들을 상속, 컴포지션, 유틸 클래스 같은 추가적인 작업 없이 손 쉽게 추가할 수 있게 한다. extension, literal function with receiver는 모두 기존 class의 멤버들에(private은 안됨) 접근 가능하도록 하지만 둘의 차이는 있다.

이 둘의 포맷이 거의 비슷하고 개념적으로도 조금 어려운 부분이 있으니 아래 예제를 보고 어떤 차이들이 있을지 확인하면 될 것 같다.

먼저 아래는 다음과 같은 구성으로 되어있다.

  • children을 추가할 수 있는 Monkey 클래스
  • 같은 기능이지만 extension function / function literal with receiver에 따라 다른 명세를 가지는 ~appendMonkey가 포함되어 있다.
  • function literal은 lambda, anonymous function 2개 모두 포함
/* Extension Function */
fun Monkey.appendMonkey(child: Monkey): Unit = addChildren(child)
/* Function literal with receiver - lambda*/
val lambdaAppendMonkey: Monkey.(child: Monkey) -> Unit = { addChildren(it) }
/* Function literal with receiver - anonymous*/
val anonymousAppendMonkey: Monkey.(child: Monkey) -> Unit = fun Monkey.(child:Monkey) = addChildren(child)

class Monkey(name: String) {
    private val children = mutableListOf<Monkey>()

    fun addChildren(child: Monkey) {
        this.children.add(child)
    }

    infix fun add(that: Monkey) = this.children.add(that)
}

fun main() {
    val parent = Monkey("parent")
    val child = Monkey("child")
    parent.appendMonkey(child) 
    parent.lambdaAppendMonkey(child)
}

이 둘을 각각 디컴파일 된 코드 기반으로 보면 이해가 쉬울 것이다.

Extension Function

먼저 extension function만 두고 디컴파일 된 코드를 보자.

기존 Monkey 클래스 내부에 새로운 메소드가 생성되지 않고 MonkeyKt 클래스가 새로 생성되었고, static final 키워드로 appendMonkey 메소드가 생성되었다.

// MonkeyKt.java
public final class MonkeyKt {
   public static final void appendMonkey(@NotNull Monkey $this$appendMonkey, @NotNull Monkey child) {
      Intrinsics.checkParameterIsNotNull($this$appendMonkey, "$this$appendMonkey");
      Intrinsics.checkParameterIsNotNull(child, "child");
      $this$appendMonkey.addChildren(child);
   }

   public static final void main() {
      Monkey parent = new Monkey("parent");
      Monkey child = new Monkey("child");
      appendMonkey(parent, child);
   }

   // $FF: synthetic method
   public static void main(String[] var0) {
      main();
   }
}


public final class Monkey {
   private final List children;

   public final void addChildren(@NotNull Monkey child) {
      Intrinsics.checkParameterIsNotNull(child, "child");
      this.children.add(child);
   }

   public final boolean add(@NotNull Monkey that) {
      Intrinsics.checkParameterIsNotNull(that, "that");
      return this.children.add(that);
   }

   public Monkey(@NotNull String name) {
      Intrinsics.checkParameterIsNotNull(name, "name");
      super();
      boolean var2 = false;
      this.children = (List)(new ArrayList());
   }
}

Function literal with receiver - Lambda

그럼 Function literal with receiver 쪽을 보자.
위와는 다르게 들어온 람다식을 Function2 타입으로 받고, 코틀린의 invoke 함수를 호출한다. (람다는 invoke 함수를 가지는 객체)

Function2의 의미는 Function2<T, U, R>, 즉 인자가 2개, 리턴 타입이 1개라는 말이고, 자바에서 코틀린의 함수를 호출할 때 인자를 함수로 넣어야한다면 이를 대신할 인터페이스를 구현하는 객체를 넣어야한다.

이 인터페이스가 FunctionN<N1, N2, ... R> 이고, invoke 함수를 abstract method로 갖는다.

main메소드를 보면 invoke(parent, child) 메소드를 호출해 람다식을 실행시킨다는 것을 알 수 있다.

Monkey 클래스는 위와 동일하니 제외시키고 MonkeyKt 클래스만 참고하면 된다.

public final class MonkeyKt {
   @NotNull
   private static final Function2 lambdaAppendMonkey;

   @NotNull
   public static final Function2 getLambdaAppendMonkey() {
      return lambdaAppendMonkey;
   }

   public static final void main() {
      Monkey parent = new Monkey("parent");
      Monkey child = new Monkey("child");
      lambdaAppendMonkey.invoke(parent, child);
   }

   // $FF: synthetic method
   public static void main(String[] var0) {
      main();
   }

   static {
      lambdaAppendMonkey = (Function2)null.INSTANCE;
   }
}

Function literal with receiver - Anonymous

익명 함수로 이루어졌더라도 똑같다. function lieteral은 같다.

public final class MonkeyKt {
   @NotNull
   private static final Function2 anonymousAppendMonkey;

   @NotNull
   public static final Function2 getAnonymousAppendMonkey() {
      return anonymousAppendMonkey;
   }

   public static final void main() {
      Monkey parent = new Monkey("parent");
      Monkey child = new Monkey("child");
      anonymousAppendMonkey.invoke(parent, child);
   }

   // $FF: synthetic method
   public static void main(String[] var0) {
      main();
   }

   static {
      anonymousAppendMonkey = (Function2)null.INSTANCE;
   }
}

비슷하면서도 다른.. 

 

이 글에서는 `this 컨텍스트 전파`에 초점을 맞추고 있으니 읽어보는 것도 괜찮은 것 같다. 

'Programming > Kotlin' 카테고리의 다른 글

11. Kotlin - Higher Order Functions  (0) 2021.01.22
10. Kotlin - Functional Interface  (0) 2021.01.22
9. Kotlin - Interface  (0) 2021.01.22
8. Kotlin - Backing Field  (0) 2021.01.22
7. Kotlin - Properties  (0) 2021.01.22
Comments