Kotlin의 Extension은 어떻게 동작하는가 part 1

Extensions 101

Kotlin은 확장(Extension)이라는 기능을 통해 간단하게 객체의 함수나 프로퍼티를 임의로 확장 정의하여 사용할 수 있습니다. Extension의 장점은 다음과 같습니다.

  • 함수와 프로퍼티 양측에 대한 확장을 지원합니다.
  • Generic을 통해 객체의 타입을 처리할 수 있습니다.
  • Extension이 적용될 범위(Scope)를 지정할 수 있습니다.

Extension function의 예

먼저 확장 함수(Extension functions)에 대해 알아보도록 하겠습니다. 만약 String 타입에 hello()라는 이름의 확장 함수를 구현하고자 한다면 다음과 같이 작성할 수 있을 것입니다.

fun String.hello() : String {
    return "Hello, $this"
}fun main(args: Array<String>) {
    val whom = "cwdoh"
    println(whom.hello())
}// Result
Hello, cwdoh

몇가지 규칙들

편리함을 주는 모든 것은 반대로 각자의 그림자를 가지고 있습니다. Extension을 사용할 때 알아야 할 몇가지 규칙을 살펴보도록 합시다.

규칙.1 Extensions은 정적으로 처리된다.

앞에서 본 예제를 디컴파일한 코드는 다음과 같습니다.

public final class ExtensionsKt {
   @NotNull
   public static final String hello(@NotNull String $receiver) {
      Intrinsics.checkParameterIsNotNull($receiver, "$receiver");
      return "Hello, " + $receiver;
   }   public static final void main(@NotNull String[] args) {
      Intrinsics.checkParameterIsNotNull(args, "args");
      String whom = "cwdoh";
      String var2 = hello(whom);
      System.out.println(var2);
   }
}
open class Cclass D: C()fun C.foo() = "c"
fun D.foo() = "d"fun printFoo(c: C) {
    println(c.foo())
}class Demo {
    fun run() {
        printFoo(D())        
    }
}
public final class Demo {
   public final void run() {
      DemoKt.printFoo((C)(new D()));
   }
}// ...public final class DemoKt {
   @NotNull
   public static final String foo(@NotNull C $receiver) {
      Intrinsics.checkParameterIsNotNull($receiver, "$receiver");
      return "c";
   }   @NotNull
   public static final String foo(@NotNull D $receiver) {
      Intrinsics.checkParameterIsNotNull($receiver, "$receiver");
      return "d";
   }   public static final void printFoo(@NotNull C c) {
      Intrinsics.checkParameterIsNotNull(c, "c");
      String var1 = foo(c);
      System.out.println(var1);
   }
}

규칙.2 Extension보다 멤버가 우선이다.

이미 클래스에 동일한 시그니처(Signature)를 가지는 멤버가 있을 때에도 Extension를 정의할 수 있습니다. 다음 예제를 봅시다.

class Person {
    fun hello() { println("hello!") }
}fun Person.hello() { println("HELLLLLLOOOOOOOOO!!!!") }fun main(args: Array<String) {
    Person().hello()
}// Result
hello!
public final class Person {
   public final void hello() {
      String var1 = "hello!";
      System.out.println(var1);
   }
}public final class ExtensionsKt {
   public static final void hello(@NotNull Person $receiver) {
      Intrinsics.checkParameterIsNotNull($receiver, "$receiver");
      String var1 = "HELLLLLLOOOOOOOOO!!!!";
      System.out.println(var1);
   }

   public static final void main(@NotNull String[] args) {
      Intrinsics.checkParameterIsNotNull(args, "args");
      (new Person()).hello();
   }
}

규칙 3. Extension 역시 범위(Scope)를 가진다.

Extensions의 선택적인 import가 가능합니다. 간단한 내용이므로, 별도로 설명은 하지 않도록 하겠습니다.

class D {
    fun bar() { println("D.bar()") }
}class C {
    fun baz() { println("C.bar()") }    fun D.foo() {
        bar()   // calls D.bar
        baz()   // calls C.baz
    }    fun caller(d: D) {
        d.foo()   // call the extension function
    }
}
public final class C {
   public final void baz() {
      String var1 = "C.bar()";
      System.out.println(var1);
   }   public final void foo(@NotNull D $receiver) {
      Intrinsics.checkParameterIsNotNull($receiver, "$receiver");
      $receiver.bar();
      this.baz();
   }   public final void caller(@NotNull D d) {
      Intrinsics.checkParameterIsNotNull(d, "d");
      this.foo(d);
   }
}public final class D {
   public final void bar() {
      String var1 = "D.bar()";
      System.out.println(var1);
   }
}
class D {
    fun bar() { println("D.bar()") }
}
class C {
    fun D.foo() {
        toString()         // calls D.toString()
        this@C.toString()  // calls C.toString()
    }
}
public final class C {
   public final void foo(@NotNull D $receiver) {
      Intrinsics.checkParameterIsNotNull($receiver, "$receiver");
      $receiver.toString();
      this.toString();
   }
}public final class D {
   public final void bar() {
      String var1 = "D.bar()";
      System.out.println(var1);
   }
}

What are Today I learned:

Extension은 인스턴스의 멤버가 아닌 정적인 별도 형태로 코드가 생성됩니다. 이는 인스턴스에 따라 멤버가 호출되는 일반적인 객체 지향/기반 형태와는 달리 코드 내의 표현식이 가지는 타입에 따라 접근 대상이 결정됨을 의미합니다.

  • 멤버 우선, import 실수 등을 방지하기 위해 되도록 쉽게 구분이 가능하도록 함수의 이름이나 시그니처(Signature)를 잘 정의하도록 하여야 합니다.
  • 특정한 클래스 내에서 사용되는 경우 멤버로 선언하여 안전하게 사용하는 것이 좋습니다.

TIL: Kotlin in practice (한국어)

Kotlin과 Android에 대한 스터디 로그와 글 모음

Chang W. Doh

Written by

Daytime Android dev. Google Developer Expert for Web. Doing something looks like development. 😎

TIL: Kotlin in practice (한국어)

Kotlin과 Android에 대한 스터디 로그와 글 모음