본문 바로가기

빈 구멍 채우기

[Kotlin] interface 안 default method를 자바로 변환하기

출처

https://kotlinlang.org/docs/java-to-kotlin-interop.html#default-methods-in-interfaces

 

Calling Kotlin from Java | Kotlin

 

kotlinlang.org

https://blog.jetbrains.com/kotlin/2020/07/kotlin-1-4-m3-generating-default-methods-in-interfaces/

 

Kotlin 1.4-M3: Generating Default Methods in Interfaces | The Kotlin Blog

In Kotlin 1.4, we’re adding new experimental ways for generating default methods in interfaces in the bytecode for the Java 8 target. Later, we’re going to be deprecating the @JvmDefault annotation in

blog.jetbrains.com

 

 


코틀린은 인터페이스에 default method를 선언할 수 있지만, 자바는 1.8부터 인터페이스 안에 default method를 기입할 수 있게 되었다. 

 

1. 다른 설정없이 코드 변환

interface Fruit {

    fun color() : String
    fun bloom() = println("blooming")
}

 

-> 자바로 컴파일

public interface Fruit {
   @NotNull
   String color();

   void bloom();

   @Metadata(
      mv = {1, 7, 0},
      k = 3
   )
   public static final class DefaultImpls {
      public static void bloom(@NotNull Fruit $this) {
         String var1 = "blooming";
         System.out.println(var1);
      }
   }
}

 

DefaultImpls 클래스를 내부 클래스로 생성한다.

 

😮 자바도 interface 안에 default method를 선언할 수 있는데, 코틀린으로 변환하면 왜 구지 내부 클래스로 생성할까?

자바 구버전과의 호환성 때문에. 자바 Java 8 미만 버전에서는 interface 안에 default method를 생성할 수 없었다.

 

2. @JvmDefault 이용

@JvmDefault는 Kotlin 1.2에서 실험적으로 등장해 코틀린의 인터페이스의 default method를 자바의 인터페이스 default method로 변환한다. Java 8을 타겟으로 한다. @JvmDefault는 Deprecated 됐다.

interface Fruit {

    fun color() : String
    @JvmDefault
    fun bloom() = println("blooming")
}

 

-> 자바로 변환

public interface Fruit {
   @NotNull
   String color();

   @JvmDefault
   default void bloom() {
      String var1 = "blooming";
      System.out.println(var1);
   }
}

 

@JvmDefault는 인터페이스의 default method에 일일이 붙여줘야하는 수고로움이 있고, 위임의 경우에 오버라이드된 디폴트 메소드가 호출되지 않는 이슈가 있다.

 

위임 이슈 확인

interface Fruit {

    fun color() : String
    @JvmDefault
    fun bloom() = println("blooming")
}

class Orange : Fruit {
    override fun color() = "orange"
    override fun bloom()  = println("end blooming")
}

class Apple(fruit: Fruit): Fruit by fruit

fun main() {
    val orange = Orange()
    val apple = Apple(orange)
    apple.bloom()
    // 결과로 "end blooming"의 출력을 예상하지만, 실제로는 "blooming"이 출력된다.
}

 

 

3. -Xjvm-default=all 또는 -Xjvm-default=all-compatibility 사용

컴파일 옵션 설정 - IntelliJ

 

코틀린 코드

interface Fruit {

    fun color() : String
    fun bloom() = println("blooming")
}

class Orange : Fruit {
    override fun color() = "orange"
    override fun bloom()  = println("end blooming")
}

class Apple(fruit: Fruit): Fruit by fruit

fun main() {
    val orange = Orange()
    val apple = Apple(orange)
    apple.bloom() // "end blooming"으로 출력됨
}

 

-> 자바 변환 코드

public interface Fruit {
   @NotNull
   String color();

   default void bloom() {
      String var1 = "blooming";
      System.out.println(var1);
   }
}

 

옵션 설명

  • -Xjvm-default=all : 더이상의 DefaultImpls 객체없이 default methods만 있을 경우
  • -Xjvm-default=all-compatibility : -Xjvm-default를 사용하는 새 스키마로 컴파일된 클래스가 이전 스키마(DefaultImpls 포함)로 컴파일된 인터페이스를 구현하는 경우

https://blog.jetbrains.com/kotlin/2020/07/kotlin-1-4-m3-generating-default-methods-in-interfaces/

 

위의 코드를 보면, DefaultItmpls를 통해 default method를 호출한다.

https://blog.jetbrains.com/kotlin/2020/07/kotlin-1-4-m3-generating-default-methods-in-interfaces/

 

그래서 이렇게  사용해도 되기 때문에, 과거 스키마를 가진 코드와 호환이 된다.

 

😮 -Xjvm-default=all-compatibility를 사용하는 경우 바이너리 호환성이 원활하지 않아 컴파일 에러가 생기는 이슈가 생길 수 있다. 이 경우에는 @JvmDefaultWithoutCompatibility를 사용해서 특정 클래스만 DefaultImpls 없이 자바 인터페이스 default method를 사용하게 할 수 있다.