Kotlin-Java 相互運用ガイド

コレクションでコンテンツを整理 必要に応じて、コンテンツの保存と分類を行います。

このドキュメントは、Java と Kotlin で相互運用可能な公開 API を作成するための一連のルールです。これらのルールは、他の言語からも違和感なくコードを使用できるようにする目的で設定されました。

最終更新日: 2018-05-18

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’s Flowable.create() メソッド シグネチャは次のように定義されます。

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

FlowableOnSubscribe は SAM 変換に有効であるため、Kotlin からのこのメソッドの関数呼び出しは次のようになります。

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

ただし、パラメータがメソッド シグネチャで反転している場合、関数呼び出しでは trailing-lambda 構文を使用できます。

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」接頭辞のないアクセサなど、標準以外の接頭辞は使用しないでください。標準以外の接頭辞を持つメソッドでも、メソッドの動作によっては、受け入れられる可能性のある関数として呼び出すことができます。

演算子のオーバーロード

Kotlin では特殊な呼び出しサイトの構文を使用できるメソッド名もあります(例: 演算子のオーバーロード)。必ず短縮構文での使用に適したメソッド名を使用してください。

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 からの使用を想定された関数型では、Unit という戻り値の型を回避する必要があります。これを行うには明示的な return Unit.INSTANCE; ステートメントの指定が必要ですが、これは慣用的な方法ではありません。

fun sayHi(callback: (String) -> Unit) = /* … */
// Kotlin caller:
greeter.sayHi { Log.d("Greeting", "Hello, $it!") }
// Java caller:
greeter.sayHi(name -> {
    Log.d("Greeting", "Hello, " + name + "!");
    return Unit.INSTANCE;
});

また、この構文では、他のタイプに実装できるようなわかりやすい名前を付けた型を指定することもできません。

ラムダ型向けに Kotlin で名前設定済みの単一抽象メソッド(SAM)インターフェースを定義すると、Java の問題は解消しますが、Kotlin でラムダ構文を使用できなくなります。

interface GreeterCallback {
    fun greetName(name: String): Unit
}

fun sayHi(callback: GreeterCallback) = /* … */
// Kotlin caller:
greeter.sayHi(object : GreeterCallback {
    override fun greetName(name: String) {
        Log.d("Greeting", "Hello, $name!")
    }
})
// Java caller:
greeter.sayHi(name -> Log.d("Greeting", "Hello, " + name + "!"))

名前設定済みの SAM インターフェースを Java で定義すると、やや下位のバージョンの Kotlin ラムダ構文を使用できるようになります。この構文では、インターフェース型を明示的に指定する必要があります。

// Defined in Java:
interface GreeterCallback {
    void greetName(String name);
}
fun sayHi(greeter: GreeterCallback) = /* … */
// Kotlin caller:
greeter.sayHi(GreeterCallback { Log.d("Greeting", "Hello, $it!") })
// Java caller:
greeter.sayHi(name -> Log.d("Greeter", "Hello, " + name + "!"));

パラメータ型を Java と Kotlin のどちらの言語からも違和感なくラムダとして使用できるように定義する方法は、今のところありません。現状では、関数型を優先するよう推奨されています。ただし、戻り値の型が Unit である場合に Java からは使いにくいという難点があります。

Nothing のジェネリクスを回避する

ジェネリック パラメータが Nothing である型は、原型(raw type)として Java に公開されます。原型が Java で使用されるのはまれなので、回避する必要があります。

例外を文書化する

チェック済みの例外をスローできる関数では、@Throws でそれらの例外を文書化する必要があります。ランタイム例外は KDoc で文書化する必要があります。

関数のデリゲート先となる API に留意してください。これらの API からはチェック済みの例外がスローされる場合があるためです。これが行われないと、Kotlin では気付かれないままチェック済みの例外が許可されます。

防御的コピー(Defensive copy)

共有または所有者不在の読み取り専用コレクションを公開 API から返すときは、変更不可能なコンテナにラップするか、防御的コピー(defensive copy)を実行してください。 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 のアノテーションを付ける必要があります。

アノテーションがない場合、これらのプロパティは、静的 Companion フィールド上の非標準名インスタンス「getters」としてしか使用できなくなります。@JvmField ではなく @JvmStatic を使用すると、非標準的な名前の「getters」は、クラス上の静的メソッドに移されますが、それでもまだ正しくありません。

誤: アノテーションがありません

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
data class Some(val value: T): Optional()
object None : Optional()

@JvmName("ofNullable")
fun  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 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] > [Preferences] > [Editor] > [Inspections] の順に移動し、Kotlin 相互運用で有効にしたいルールにチェックを入れます。

図 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 を実行します。