Kotlin-자바 상호 운용성 가이드

이 문서는 Java와 Kotlin에서 공개 API를 작성하기 위한 규칙 모음입니다. 코드가 다른 호출에서 사용될 때 자연스럽다고 느끼게 하려는 의도로 있습니다.

최종 업데이트: 2024년 7월 29일

자바(Kotlin 사용의 경우)

하드 키워드 없음

Kotlin의 하드 키워드를 메서드 이름으로 사용하지 마세요. 지정할 수 있습니다 호출 시 백틱을 사용하여 이스케이프 처리해야 합니다. 있습니다. 소프트 키워드, 수정자 키워드특수 식별자는 허용됩니다.

예를 들어 Mockito의 when 함수를 Kotlin에서 사용할 때는 백틱이 필요합니다.

val callable = Mockito.mock(Callable::class.java)
Mockito.`when`(callable.call()).thenReturn(/* … */)

Any 확장 프로그램 이름 피하기

다음 경우에 Any의 확장 함수 이름을 사용하지 마세요. 다음 인스턴스의 Any의 확장 속성 메서드 또는 이름 필드를 설정할 수 있습니다. 멤버 메서드와 필드는 항상 Any의 확장 함수 또는 속성보다 우선하는 경우 다음과 같을 수 있습니다. 코드를 읽을 때 어떤 것이 호출되고 있는지 알기 어렵습니다.

null 허용 여부 주석

공개 API의 원시가 아닌 모든 매개변수, 반환, 필드 유형은 null 허용 여부 주석이 있어야 합니다. 주석 처리되지 않은 유형은 다음과 같이 해석됩니다. 'platform' 유형이 있으며, null 허용 여부가 모호합니다.

기본적으로 Kotlin 컴파일러 플래그는 JSR 305 주석을 준수하지만 플래그를 지정합니다. 있습니다. 컴파일러가 주석을 오류로 처리하도록 플래그를 설정할 수도 있습니다.

마지막 람다 매개변수

SAM 변환에 적합한 매개변수 유형은 마지막이어야 합니다.

예를 들어 RxJava 2의 Flowable.create() 메서드 서명은 다음과 같이 정의됩니다.

public static <T> Flowable<T> create(
    FlowableOnSubscribe<T> source,
    BackpressureStrategy mode) { /* … */ }

FlowableOnSubscription은 SAM 변환에 적합하므로 이 메서드는 다음과 같습니다.

Flowable.create({ /* … */ }, BackpressureStrategy.LATEST)

그러나 매개변수가 메서드 서명에서 역전된 경우 함수 호출은 후행 람다 문법을 사용할 수 있습니다.

Flowable.create(BackpressureStrategy.LATEST) { /* … */ }

속성 접두사

Kotlin에서 메서드를 속성으로 나타내려면 엄격한 'bean' 스타일 접두사를 사용해야 합니다.

접근자 메서드에는 get 접두사가 필요하며 불리언을 반환하는 메서드의 경우 is가 필요합니다. 접두어를 사용할 수 있습니다.

public final class User {
  public String getName() { /* … */ }
  public boolean isActive() { /* … */ }
}
val name = user.name // Invokes user.getName()
val active = user.isActive // Invokes user.isActive()

연결된 뮤테이터 메서드에는 set 접두사가 필요합니다.

public final class User {
  public String getName() { /* … */ }
  public void setName(String name) { /* … */ }
  public boolean isActive() { /* … */ }
  public void setActive(boolean active) { /* … */ }
}
user.name = "Bob" // Invokes user.setName(String)
user.isActive = true // Invokes user.setActive(boolean)

메서드를 속성으로 노출하려면 has, set 또는 get가 아닌 접두사가 붙은 접근자. 비표준 프리픽스가 있는 메서드 는 여전히 함수로 호출할 수 있으며 이는 메서드 동작입니다.

연산자 오버로드

특수 호출 사이트 구문 (예: 연산자 오버로드 사용). 메서드의 이름이 단축 구문과 함께 사용하는 것이 합리적입니다.

public final class IntBox {
  private final int value;
  public IntBox(int value) {
    this.value = value;
  }
  public IntBox plus(IntBox other) {
    return new IntBox(value + other.value);
  }
}
val one = IntBox(1)
val two = IntBox(2)
val three = one + two // Invokes one.plus(two)

Kotlin(자바 사용의 경우)

파일 이름

파일에 최상위 함수 또는 속성이 포함되어 있으면 항상 파일에 주석을 추가합니다. @file:JvmName("Foo")를 사용하여 멋진 이름을 제공합니다.

기본적으로 MyClass.kt 파일의 최상위 멤버는 MyClassKt: 매력적이지 않고 구현으로 언어를 유출합니다. 자세히 알아보세요.

@file:JvmMultifileClass를 추가하여 여러 파일을 단일 클래스로 만들 수 있습니다.

람다 인수

Java로 정의된 단일 메서드 인터페이스 (SAM)를 Kotlin과 두 언어 모두로 구현할 수 있음 관용적인 방식으로 구현을 인라인하는 람다 문법을 사용하는 Java 있습니다. Kotlin에는 이러한 인터페이스를 정의하는 여러 옵션이 있으며, 옵션마다 약간 차이가 있습니다.

선호되는 정의

Java에서 사용하기 위한 고차 함수 Unit를 반환하는 함수 유형을 사용하면 안 됩니다. Java 호출자가 Unit.INSTANCE를 반환해야 합니다. 함수를 인라인 처리하는 대신 서명에 입력할 때는 기능 (SAM) 인터페이스를 사용하세요. 또한 일반 인터페이스 대신 기능적 (SAM) 인터페이스 사용을 고려해 보세요. 람다로 사용될 것으로 예상되는 인터페이스를 정의할 때 인터페이스 이를 통해 Kotlin에서 관용적으로 사용할 수 있습니다.

다음 Kotlin 정의를 고려해 보세요.

fun interface GreeterCallback {
  fun greetName(String name)
}

fun sayHi(greeter: GreeterCallback) = /* … */

Kotlin에서 호출되는 경우:

sayHi { println("Hello, $it!") }

Java에서 호출되는 경우:

sayHi(name -> System.out.println("Hello, " + name + "!"));

함수 유형이 Unit을 반환하지 않는 경우에도 호출자가 Kotlin, Java에서 모두 람다만이 아니라 이름이 지정된 클래스로 구현할 수 있도록 이름이 지정된 인터페이스로 만드는 것이 좋을 수 있습니다.

class MyGreeterCallback : GreeterCallback {
  override fun greetName(name: String) {
    println("Hello, $name!");
  }
}

Unit을 반환하는 함수 유형 피하기

다음 Kotlin 정의를 고려해 보세요.

fun sayHi(greeter: (String) -> Unit) = /* … */

다음과 같이 Java 호출자가 Unit.INSTANCE를 반환해야 합니다.

sayHi(name -> {
  System.out.println("Hello, " + name + "!");
  return Unit.INSTANCE;
});

구현에 상태가 있어야 하는 경우 기능 인터페이스 피하기

인터페이스 구현이 상태를 보유해야 하는 경우 람다 문법을 사용하는 것은 적합하지 않습니다. 비교적 가능이 대표적인 예입니다. thisother와 비교하기 위한 것이고 람다에 this가 없기 때문입니다. 비 인터페이스 앞에 fun를 붙이면 호출자가 object : ...를 사용하게 됩니다. 문법은 호출자에게 힌트를 제공하여 상태를 가질 수 있도록 합니다.

다음 Kotlin 정의를 고려해 보세요.

// No "fun" prefix.
interface Counter {
  fun increment()
}

Kotlin에서 람다 문법을 방지하므로 다음과 같이 더 긴 버전이 필요합니다.

runCounter(object : Counter {
  private var increments = 0 // State

  override fun increment() {
    increments++
  }
})

Nothing 제네릭 피하기

제네릭 매개변수가 Nothing인 유형은 Java에 원시 유형으로 노출됩니다. 원시 데이터 유형은 Java에서 거의 사용되지 않으므로 피해야 합니다.

예외 문서화하기

확인된 예외를 발생시킬 수 있는 함수는 @Throws로 문서화해야 합니다. 런타임 예외는 KDoc에 문서화되어야 합니다.

함수가 위임하는 API에 유의해야 합니다. 다른 경우라면 Kotlin이 자동으로 전파할 수 있는 확인된 예외를 발생시킬 수 있기 때문입니다.

방어적 복사

공개 API에서 공유 또는 소유되지 않은 읽기 전용 컬렉션을 반환할 때 래핑합니다. 방어적인 사본을 수행할 수 있습니다 Kotlin에도 불구하고 읽기 전용 속성을 적용하는 경우 Java에는 있습니다 래퍼 또는 방어적 복사가 없으면 불변이 수명이 긴 컬렉션 참조를 반환합니다.

컴패니언 함수

컴패니언 객체의 공개 함수는 @JvmStatic로 주석을 달아야 합니다. 정적 메서드로 노출되도록 합니다.

주석이 없으면 이러한 함수는 인스턴스 메서드로만 사용할 수 있습니다. 정적 Companion 필드에 위치해야 합니다.

올바르지 않음: 주석 없음

class KotlinClass {
    companion object {
        fun doWork() {
            /* … */
        }
    }
}
public final class JavaClass {
    public static void main(String... args) {
        KotlinClass.Companion.doWork();
    }
}

올바름: @JvmStatic 주석

class KotlinClass {
    companion object {
        @JvmStatic fun doWork() {
            /* … */
        }
    }
}
public final class JavaClass {
    public static void main(String... args) {
        KotlinClass.doWork();
    }
}

컴패니언 상수

companion object에서 효과적인 상수인 const가 아닌 공개 속성은 정적 필드로 노출되도록 @JvmField로 주석을 달아야 합니다.

주석이 없으면 이러한 속성은 이상하게 이름이 지정된 속성으로만 사용할 수 있습니다. 'getters' 인스턴스 정적 Companion 필드에 배치해야 합니다. 대신 @JvmStatic 사용 이상하게 이름이 지정된 'getters'를 이동시키는 @JvmField의 비율 클래스의 정적 메서드와 이는 여전히 정확하지 않습니다.

올바르지 않음: 주석 없음

class KotlinClass {
    companion object {
        const val INTEGER_ONE = 1
        val BIG_INTEGER_ONE = BigInteger.ONE
    }
}
public final class JavaClass {
    public static void main(String... args) {
        System.out.println(KotlinClass.INTEGER_ONE);
        System.out.println(KotlinClass.Companion.getBIG_INTEGER_ONE());
    }
}

올바르지 않음: @JvmStatic 주석

class KotlinClass {
    companion object {
        const val INTEGER_ONE = 1
        @JvmStatic val BIG_INTEGER_ONE = BigInteger.ONE
    }
}
public final class JavaClass {
    public static void main(String... args) {
        System.out.println(KotlinClass.INTEGER_ONE);
        System.out.println(KotlinClass.getBIG_INTEGER_ONE());
    }
}

올바름: @JvmField 주석

class KotlinClass {
    companion object {
        const val INTEGER_ONE = 1
        @JvmField val BIG_INTEGER_ONE = BigInteger.ONE
    }
}
public final class JavaClass {
    public static void main(String... args) {
        System.out.println(KotlinClass.INTEGER_ONE);
        System.out.println(KotlinClass.BIG_INTEGER_ONE);
    }
}

직관적인 이름 지정

Kotlin에는 함수 이름의 지정 방식을 변경할 수 있는 자바와는 다른 호출 규칙이 있습니다. @JvmName을 사용하여 관용적으로 느껴지도록 이름을 디자인합니다. 를 사용하거나 각 표준 라이브러리와 일치해야 합니다. 이름을 지정합니다

이 문제는 확장 함수 및 확장 속성에서 가장 자주 발생합니다. 수신자 유형의 위치가 다르기 때문입니다.

sealed class Optional<T : Any>
data class Some<T : Any>(val value: T): Optional<T>()
object None : Optional<Nothing>()

@JvmName("ofNullable")
fun <T> T?.asOptional() = if (this == null) None else Some(this)
// FROM KOTLIN:
fun main(vararg args: String) {
    val nullableString: String? = "foo"
    val optionalString = nullableString.asOptional()
}
// FROM JAVA:
public static void main(String... args) {
    String nullableString = "Foo";
    Optional<String> optionalString =
          Optionals.ofNullable(nullableString);
}

기본값의 함수 오버로드

기본값이 있는 매개변수가 포함된 함수는 @JvmOverloads를 사용해야 합니다. 이 주석이 없으면 기본값을 사용하여 함수를 호출할 수 없습니다.

@JvmOverloads를 사용할 때는 생성된 메서드를 검사하여 각각이 맞는지 확인합니다. 이해가 되셨기를 바랍니다. 그렇지 않으면 다음 리팩터링 중 하나 또는 둘 다를 실행합니다. 충족될 때까지:

  • 매개변수 순서를 변경하여 있습니다.
  • 기본값을 수동 함수 오버로드로 이동합니다.

올바르지 않음: @JvmOverloads 없음

class Greeting {
    fun sayHello(prefix: String = "Mr.", name: String) {
        println("Hello, $prefix $name")
    }
}
public class JavaClass {
    public static void main(String... args) {
        Greeting greeting = new Greeting();
        greeting.sayHello("Mr.", "Bob");
    }
}

올바름: @JvmOverloads 주석

class Greeting {
    @JvmOverloads
    fun sayHello(prefix: String = "Mr.", name: String) {
        println("Hello, $prefix $name")
    }
}
public class JavaClass {
    public static void main(String... args) {
        Greeting greeting = new Greeting();
        greeting.sayHello("Bob");
    }
}

린트 검사

요구사항

  • Android 스튜디오 버전: 3.2 Canary 10 이상
  • Android Gradle 플러그인 버전: 3.2 이상

지원되는 검사

이제 Android Lint 검사에서 디코더의 일부를 감지하고 플래그를 상호 운용성 문제를 해결합니다. Java 관련 문제 (Kotlin의 경우) 감지됩니다. 구체적으로 지원되는 검사는 다음과 같습니다.

  • 알 수 없는 nullness
  • 속성 액세스
  • Kotlin 하드 키워드 없음
  • 마지막 람다 매개변수

Android 스튜디오

이러한 검사를 사용 설정하려면 파일 > 환경설정 > 편집기 > 검사 및 Kotlin 상호 운용성에서 사용 설정할 규칙을 확인합니다.

그림 1. Android 스튜디오의 Kotlin 상호운용성 설정

사용 설정하려는 규칙을 선택하면 새로운 검사가 코드 검사 (Analyze > Inspect Code...)를 실행할 때 실행

명령줄 빌드

명령줄 빌드에서 이러한 검사를 사용 설정하려면 다음 줄을 추가하세요. 내 build.gradle 파일:

Groovy

android {

    ...

    lintOptions {
        enable 'Interoperability'
    }
}

Kotlin

android {
    ...

    lintOptions {
        enable("Interoperability")
    }
}

lintOptions 내에서 지원되는 전체 구성 세트는 Android Gradle DSL 참조를 읽어보세요.

그런 다음 ./gradlew lint를 명령줄에서 실행합니다.