[Kotlin] JavaからKotlinを呼び出す

※翻訳がわかりにくい場合は原文を読んでください。

KotlinコードはJavaから簡単に呼び出すことができます。

プロパティ

Kotlinプロパティは、次のJava要素にコンパイルされます。

  • get接頭辞を前置することによって計算された名前を持つgetterメソッド
  • set接頭辞を前置することによって計算された名前を持つsetterメソッド(varプロパティに対してのみ)
  • プロパティ名と同じ名前のプライベートフィールド(バッキングフィールドを持つプロパティのみ)

例えば、 var firstName: String は以下のJava宣言にコンパイルされます

private String firstName;

public String getFirstName() {
    return firstName;
}

public void setFirstName(String firstName) {
    this.firstName = firstName;
}

プロパティの名前が isで始まる場合、別の名前マッピング規則が使用されます。ゲッターの名前は次のようになります。 プロパティ名と同じで、setterの名前は issetに置き換えることで得られます。 たとえば、プロパティisOpenの場合、getterはisOpen()と呼ばれ、setterはsetOpen()と呼ばれます。 このルールは、 Booleanだけでなくあらゆるタイプのプロパティに適用されます。

パッケージレベルの関数

拡張機能を含む org.foo.barパッケージ内のexample.ktファイル内で宣言されたすべての関数とプロパティは、 org.foo.bar.ExampleKt`という名前のJavaクラスの静的メソッドにコンパイルされます。

// example.kt
package demo

class Foo

fun bar() {
}
// Java
new demo.Foo();
demo.ExampleKt.bar();

生成されたJavaクラスの名前は、 @JvmNameアノテーションを使用して変更できます。

@file:JvmName("DemoUtils")

package demo

class Foo

fun bar() {
}
// Java
new demo.Foo();
demo.DemoUtils.bar();

生成された同じJavaクラス名を持つ複数のファイルを持つ(同じパッケージで同じ名前または同じ @JvmNameアノテーション)は通常エラーです。 ただし、コンパイラには、指定された名前を持ち、その名前を持つすべてのファイルのすべての宣言が含まれる単一のJavaファサードクラスを生成する機能があります。 このようなファサードの生成を可能にするには、すべてのファイルで@JvmMultifileClassアノテーションを使用します。

// oldutils.kt
@file:JvmName("Utils")
@file:JvmMultifileClass

package demo

fun foo() {
}
// newutils.kt
@file:JvmName("Utils")
@file:JvmMultifileClass

package demo

fun bar() {
}
// Java
demo.Utils.foo();
demo.Utils.bar();

インスタンスフィールド

JavaのフィールドとしてKotlinプロパティを公開する必要がある場合は、それを @JvmFieldアノテーションでアノテートする必要があります。 フィールドは、基になるプロパティと同じ可視性を持ちます。 @JvmFieldにバッキングフィールドがある、プライベートでない、 openoverrideconst修飾子を持たず、委譲されたプロパティでない場合、プロパティに注釈を付けることができます。

class C(id: String) {
    @JvmField val ID = id
}
// Java
class JavaClient {
    public String getID(C c) {
        return c.ID;
    }
}

Late-Initialized プロパティもフィールドとして公開されます。 フィールドの可視性は、 lateinitプロパティーセッターの可視性と同じになります。

静的フィールド

名前付きオブジェクトまたはコンパニオンオブジェクトで宣言されたKotlinプロパティは、名前付きオブジェクトまたはコンパニオンオブジェクトを含むクラスの静的なバッキングフィールドを持ちます。

通常、これらのフィールドはプライベートですが、次のいずれかの方法で公開することができます。

  • @JvmField アノテーション
  • lateinit 修飾子
  • const 修飾子

このようなプロパティに @JvmFieldを付けると、プロパティ自体と同じ可視性を持つ静的フィールドになります。

class Key(val value: Int) {
    companion object {
        @JvmField
        val COMPARATOR: Comparator<Key> = compareBy<Key> { it.value }
    }
}
// Java
Key.COMPARATOR.compare(key1, key2);
// public static final field in Key class

オブジェクトまたはコンパニオンオブジェクトの late-initialized プロパティはプロパティセッターと同じ可視性を持つ静的なバッキングフィールドを持っています。

object Singleton {
    lateinit var provider: Provider
}
// Java
Singleton.provider = new Provider();
// public static non-final field in Singleton class

constでアノテーションされたプロパティ(クラス内とトップレベル)は、Javaの静的フィールドに変換されます

// file example.kt

object Obj {
    const val CONST = 1
}

class C {
    companion object {
        const val VERSION = 9
    }
}

const val MAX = 239

Javaの場合:

int c = Obj.CONST;
int d = ExampleKt.MAX;
int v = C.VERSION;

静的メソッド

前述のように、Kotlinは静的メソッドとしてパッケージレベルの関数を表します。 これらの関数に @JvmStaticと注釈を付けると、Kotlinは名前付きオブジェクトやコンパニオンオブジェクトで定義された関数の静的メソッドを生成することもできます。 このアノテーションを使用すると、コンパイラはオブジェクトの囲むクラスに静的メソッドを生成し、オブジェクト自体にインスタンスメソッドを生成します。 例えば、

class C {
    companion object {
        @JvmStatic fun foo() {}
        fun bar() {}
    }
}

今、 foo()はJavaでは静的ですが、 bar()は静的ではありません

C.foo(); // works fine
C.bar(); // error: not a static method
C.Companion.foo(); // instance method remains
C.Companion.bar(); // the only way it works

名前付きオブジェクトと同じ

object Obj {
    @JvmStatic fun foo() {}
    fun bar() {}
}

Javaの場合:

Obj.foo(); // works fine
Obj.bar(); // error
Obj.INSTANCE.bar(); // works, a call through the singleton instance
Obj.INSTANCE.foo(); // works too

@JvmStaticアノテーションは、オブジェクトまたはコンパニオンオブジェクトのプロパティに適用することもできます。そのため、getterメソッドとsetterメソッドは、そのオブジェクトまたはコンパニオンオブジェクトを含むクラスの静的メンバになります。

## 可視性

Kotlinの可視性は、次のようにJavaにマッピングされます。

  • privateメンバはprivateメンバにコンパイルされます。
  • privateトップレベル宣言はパッケージローカル宣言にコンパイルされます。
  • protectedprotectedのままです(Javaは同じパッケージ内の他のクラスから保護されたメンバーにアクセスすることができます Kotlinはそうしないので、Javaクラスはコードへのより広いアクセスを持つでしょう)。
  • internal宣言はJavaでpublicになります。 internalクラスのメンバは、誤ってJavaからそれらを使用するのを困難にし、Kotlinの規則に従ってお互いに見えない同じ署名を持つメンバのためにオーバーロードを許容するために、名前修飾を受けます。
  • publicpublicのままです。

## Kクラス

時々 KClass型のパラメータでKotlinメソッドを呼び出す必要があります。 ClassからKClassへの自動変換はありませんので、 Class <T> .kotlin拡張プロパティと同等のものを呼び出すことで手動で行う必要があります:

kotlin.jvm.JvmClassMappingKt.getKotlinClass(MainView.class)

@JvmNameで署名の衝突を処理する

Kotlinには、バイトコードとは異なるJVM名が必要な名前付き関数があります。 最も顕著な例は型消去のために起こります。

fun List<String>.filterValid(): List<String>
fun List<Int>.filterValid(): List<Int>

これら2つの関数は、JVMのシグネチャが同じ filterValid(Ljava/util/List;)Ljava/util/List; であるため、並行して定義することはできません。 Kotlinに同じ名前をつけたいのであれば、そのうちの1つ(または両方)に @JvmNameを付けて、別の名前を引数として指定することができます。

fun List<String>.filterValid(): List<String>

@JvmName("filterValidInt")
fun List<Int>.filterValid(): List<Int>

Kotlinからは filterValidと同じ名前でアクセスできますが、JavaからはfilterValidfilterValidIntがあります。

同じトリックが、 getX()関数の横にプロパティ xを持つ必要があるときに適用されます。

val x: Int
    @JvmName("getX_prop")
    get() = 15

fun getX() = 10

オーバーロード生成

通常、デフォルトのパラメータ値を持つKotlin関数を記述すると、Javaでは完全なシグネチャとしてのみ表示され、すべてのパラメータが表示されます。複数のオーバーロードをJava呼び出し側に公開する場合は、 @JvmOverloadsアノテーションを使用できます。

注釈は、コンストラクタ、静的メソッドなどにも使用できます。インタフェースで定義されたメソッドを含む抽象メソッドでは使用できません。

class Foo @JvmOverloads constructor(x: Int, y: Double = 0.0) {
    @JvmOverloads fun f(a: String, b: Int = 0, c: String = "abc") {
        ...
    }
}

デフォルト値を持つすべてのパラメータについて、これは追加のオーバーロードを1つ生成します。このオーバーロードには、このパラメータがあり、パラメータリストのその右側のすべてのパラメータが削除されます。この例では、次のものが生成されます。

// Constructors:
Foo(int x, double y)
Foo(int x)

// Methods
void f(String a, int b, String c) { }
void f(String a, int b) { }
void f(String a) { }

Secondary Constructorsで説明されているように、クラスにすべてのコンストラクタパラメータのデフォルト値がある場合は、引数なしのpublicコンストラクタが生成されます。これは、@JvmOverloadsアノテーションが指定されていなくても機能します。

チェックされた例外

上記のように、Kotlinは例外をチェックしていません。 したがって、通常、Kotlin関数のJavaシグネチャは例外がスローされたことを宣言しません。 したがって、Kotlinに次のような関数があるとします。

// example.kt
package demo

fun foo() {
    throw IOException()
}

そして、Javaから呼び出して例外をキャッチしたい

// Java
try {
  demo.Example.foo();
}
catch (IOException e) { // error: foo() does not declare IOException in the throws list
  // ...
}

foo()IOExceptionを宣言しないので、Javaコンパイラからエラーメッセージが出ます。 この問題を回避するには、Kotlinで @Throwsアノテーションを使用してください。

@Throws(IOException::class)
fun foo() {
    throw IOException()
}

Null-safety

JavaからKotlin関数を呼び出すときに、null以外のパラメータとして null{: .keyword } を渡すことができなくなります。 だからこそ、Kotlinは、非nullを期待するすべてのパブリック関数のランタイムチェックを生成します。 このようにして、Javaコードで即座に NullPointerExceptionを取得します。

バリアントジェネリックス

Kotlinクラスが 宣言サイトの分散 を利用する場合、 その使用法がJavaコードからどのように見えるかという2つのオプションがあります。次のクラスとそれを使用する2つの関数があるとしましょう。

class Box<out T>(val value: T)

interface Base
class Derived : Base

fun boxDerived(value: Derived): Box<Derived> = Box(value)
fun unboxBase(box: Box<Base>): Base = box.value

これらの関数をJavaに変換する素朴な方法は次のとおりです。

Box<Derived> boxDerived(Derived value) { ... }
Base unboxBase(Box<Base> box) { ... }

問題は、Kotlinでは unboxBase(boxDerived("s")) と言うことができますが、Javaでは不可能です。なぜなら、JavaではクラスBoxはパラメータT不変であるため、従って Box<Derived>Box<Base> のサブタイプではありません。 Javaで動作させるには、次のように unboxBaseを定義する必要があります。

Base unboxBase(Box<? extends Base> box) { ... }  

ここでは、Javaのワイルドカード型? extends Base)を使用して、使用サイトの分散によって宣言サイトの分散をエミュレートします。なぜなら、それはJavaが持つすべてのものだからです。

Kotlin APIをJavaで動作させるために、パラメータとして現れるときに、共変に定義された Box(反逆的に定義されたFooの場合は Foo<? super Bar>)のBox<Super>Box<? extends Super>として生成します。戻り値の場合、ワイルドカードは生成されません。そうしなければ、Javaクライアントは対処する必要があります(一般的なJavaコーディングスタイルに反します)。したがって、この例の関数は実際には次のように変換されます。

// return type - no wildcards
Box<Derived> boxDerived(Derived value) { ... }

// parameter - wildcards 
Base unboxBase(Box<? extends Base> box) { ... }

注意:引数の型がfinalの場合、通常はワイルドカードを生成する点はないので、どの位置にあっても Box <String>は常に Box <String>です。

デフォルトで生成されないワイルドカードが必要な場合は、 @JvmWildcardアノテーションを使用できます。

fun boxDerived(value: Derived): Box<@JvmWildcard Derived> = Box(value)
// is translated to 
// Box boxDerived(Derived value) { ... }

一方、生成されるワイルドカードが必要ない場合は、 @ JvmSuppressWildcardsを使用することができます。

fun unboxBase(box: Box<@JvmSuppressWildcards Base>): Base = box.value
// is translated to 
// Base unboxBase(Box<Base> box) { ... }

注意: @JvmSuppressWildcardsは、個々の型引数だけでなく、関数やクラスなどの宣言全体でも使用でき、その中のすべてのワイルドカードを抑止できます。

Nothing型の翻訳

Nothing は、Javaに自然な対応がないため特別です。 実際、 java.lang.Voidを含むすべてのJava参照型は値としてnullを受け取り、 Nothingはそれを受け入れません。したがって、このタイプはJavaの世界では正確に表現できません。 これがKotlinが Nothing型の引数が使われる生の型を生成する理由です:

fun emptyList(): List<Nothing> = listOf()
// is translated to
// List emptyList() { ... }