Kotlin-Java 相互運用ガイド

このドキュメントは、Java と Kotlin で公開 API を作成するための一連のルールです。 他の依存関係から消費されても、コードが慣用的に感じられる あります。

最終更新日: 2024 年 7 月 29 日

Java(Kotlin で使用する場合)

ハード キーワードなし

Kotlin のハード キーワードをメソッド名として使用しない 定義できます。これらを使用するには、呼び出しの際にバッククォートを使用してエスケープする必要があります。 Kotlin。ソフト キーワード修飾子キーワード 特別な識別子を使用できます。

たとえば、Mockito の when 関数を Kotlin から使用する場合は、バッククォートが必要です。

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

Any 拡張機能名を回避する

以下では、Any の拡張関数の名前を使用しないでください。 メソッドまたは Any の拡張プロパティの名前 使用しないでください。メンバーのメソッドとフィールドは常に Any の拡張関数またはプロパティよりも優先されます。必要に応じて、 どの関数が呼び出されているかを 知るのが難しくなります

null 値許容アノテーション

公開 API のすべての非プリミティブ パラメータ、戻り値、フィールド型は、 null 可能性アノテーションがあります。アノテーションが付いていない型は、 「プラットフォーム」null 可能性があいまいなデータ型があります。

デフォルトでは、Kotlin コンパイラ フラグは JSR 305 アノテーションを受け入れますが、それらにフラグを立てます。 表示されます。コンパイラがアノテーションをエラーとして処理するようにフラグを設定することもできます。

ラムダ パラメータを最後に置く

SAM コンバージョンの対象となるパラメータ タイプは最後に配置します。

たとえば、RxJava 2 の Flowable.create() メソッド シグネチャは次のように定義されます。

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

FlowableOnSubscribe は SAM 変換の対象となるため、 Kotlin のこのメソッドは次のようになります。

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)

メソッドをプロパティとして公開したい場合、 hasset、または 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(Java で使用する場合)

ファイル名

最上位の関数またはプロパティがファイルに含まれている場合は、必ずアノテーションを付ける 適切な名前を指定するために @file:JvmName("Foo") を追加します。

デフォルトでは、MyClass.kt ファイル内のトップレベル メンバーは MyClassKt: 魅力的でなく、実装の文言が漏洩しています できます。

@file:JvmMultifileClass を追加して、次のトップレベル メンバーを組み合わせることを検討してください。 1 つのクラスにまとめることができます。

ラムダ引数

Java で定義されたシングル メソッド インターフェース(SAM)は、Kotlin と というラムダ構文を使用します。これにより、慣用的な できます。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;
});

状態を持つことを意図した実装の場合は機能インターフェースを避ける

インターフェースの実装に状態を含める場合は、ラムダ 意味がなくなります。Comparable は代表的な例です。 これは 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 に留意してください。これらの API からはチェック済みの例外がスローされる場合があるためです。これが行われないと、Kotlin では気付かれないままチェック済みの例外が許可されます。

防御のためのコピー

公開 API から共有または所有されていない読み取り専用コレクションを返す場合は、 コピーを実行したり、防御的コピーを実行したりできます。それにもかかわらず、 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 を使用 @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 には Java とは異なる呼び出し規約があり、それによって関数の命名方法が変わる場合があります。@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");
    }
}

lint チェック

要件

  • Android Studio バージョン: 3.2 Canary 10 以降
  • Android Gradle プラグイン バージョン: 3.2 以降

サポート対象のチェック

Android lint チェックを利用できるようになりました。これにより、 相互運用性の問題について説明します。Java の問題のみ(Kotlin の場合) 検出されます。サポート対象のチェックは次のとおりです。

  • null 値許容性が不明
  • プロパティのアクセス
  • ハードな Kotlin キーワードがない
  • ラムダ パラメータが最後にある

Android Studio

このチェックを有効にするには、[File] >設定 >エディタ >検査と [Kotlin Interoperability] で有効にするルールにチェックを入れます。

図 1. Android Studio の Kotlin 相互運用設定

有効にするルールのチェックを終えると、新しいチェックが コード インスペクションの実行時に実行する([Analyze] > [Inspect Code...]

コマンドライン ビルド

コマンドライン ビルドからこれらのチェックを有効にするには、次の行を build.gradle ファイルを次のように変更します。

Groovy

android {

    ...

    lintOptions {
        enable 'Interoperability'
    }
}

Kotlin

android {
    ...

    lintOptions {
        enable("Interoperability")
    }
}

lintOptions 内でサポートされる全構成については、Android Gradle DSL リファレンスを参照してください。

その後、コマンドラインから ./gradlew lint を実行します。