JavaからKotlinを呼び出す
KotlinのコードはJavaから簡単に呼び出すことができます。 例えば、Kotlinクラスのインスタンスは、Javaのメソッド内でシームレスに作成して操作できます。 ただし、KotlinのコードをJavaに統合する際には、JavaとKotlinの間の特定の相違点に注意する必要があります。 このページでは、KotlinコードとJavaクライアントとの相互運用性(interop)を調整する方法について説明します。
プロパティ (Properties)
Kotlinのプロパティは、以下のJava要素にコンパイルされます:
getプレフィックスを付けて算出された名前を持つゲッターメソッド。setプレフィックスを付けて算出された名前を持つセッターメソッド(varプロパティの場合のみ)。- プロパティ名と同じ名前を持つ非公開(private)フィールド(バッキングフィールドを持つプロパティの場合のみ)。
例えば、var firstName: String は、以下のJava宣言にコンパイルされます:
private String firstName;
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}プロパティ名が is で始まる場合、別の名前マッピング規則が使用されます。ゲッターの名前はプロパティ名と同じになり、セッターの名前は is を set に置き換えたものになります。 例えば、プロパティ isOpen の場合、ゲッターは isOpen() と呼ばれ、セッターは setOpen() と呼ばれます。 この規則は、Boolean だけでなく、あらゆる型のプロパティに適用されます。
パッケージレベルの関数 (Package-level functions)
パッケージ org.example 内のファイル app.kt で宣言されたすべての関数とプロパティ(拡張関数を含む)は、org.example.AppKt という名前のJavaクラスの静的メソッドにコンパイルされます。
// app.kt
package org.example
class Util
fun getTime() { /*...*/ }// Java
new org.example.Util();
org.example.AppKt.getTime();生成されるJavaクラスにカスタム名を指定するには、@JvmName アノテーションを使用します:
@file:JvmName("DemoUtils")
package org.example
class Util
fun getTime() { /*...*/ }// Java
new org.example.Util();
org.example.DemoUtils.getTime();生成されるJavaクラス名が同じ(同じパッケージで同じ名前、または同じ @JvmName アノテーションを持つ)複数のファイルが存在することは、通常はエラーになります。 しかし、コンパイラは、指定された名前を持ち、その名前を持つすべてのファイルからのすべての宣言を含む単一のJavaファサード(facade)クラスを生成できます。 このようなファサードの生成を有効にするには、該当するすべてのファイルで @JvmMultifileClass アノテーションを使用します。
// oldutils.kt
@file:JvmName("Utils")
@file:JvmMultifileClass
package org.example
fun getTime() { /*...*/ }// newutils.kt
@file:JvmName("Utils")
@file:JvmMultifileClass
package org.example
fun getDate() { /*...*/ }// Java
org.example.Utils.getTime();
org.example.Utils.getDate();インスタンスフィールド (Instance fields)
KotlinのプロパティをJavaのフィールドとして公開する必要がある場合は、@JvmField アノテーションを付加します。 フィールドは、基になるプロパティと同じ可視性を持ちます。以下の条件を満たす場合、プロパティに @JvmField を付加できます:
- バッキングフィールドを持っている
- privateではない
open、override、const修飾子を持っていない- 委譲プロパティ(delegated property)ではない
class User(id: String) {
@JvmField val ID = id
}
// Java
class JavaClient {
public String getID(User user) {
return user.ID;
}
}遅延初期化(Late-Initialized)プロパティもフィールドとして公開されます。 フィールドの可視性は、lateinit プロパティのセッターの可視性と同じになります。
静的フィールド (Static fields)
名前付きオブジェクト(named object)またはコンパニオンオブジェクト(companion object)で宣言されたKotlinのプロパティは、その名前付きオブジェクト内、またはコンパニオンオブジェクトを含むクラス内に静的なバッキングフィールドを持ちます。
通常、これらのフィールドはprivateですが、以下のいずれかの方法で公開できます:
@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);
// Keyクラス内の public static final フィールドオブジェクトまたはコンパニオンオブジェクト内の 遅延初期化 プロパティは、プロパティのセッターと同じ可視性を持つ静的なバッキングフィールドを持ちます。
object Singleton {
lateinit var provider: Provider
}
// Java
Singleton.provider = new Provider();
// Singletonクラス内の public static non-final フィールドconst として宣言されたプロパティ(クラス内およびトップレベル)は、Javaでは静的フィールドに変換されます:
// ファイル example.kt
object Obj {
const val CONST = 1
}
class C {
companion object {
const val VERSION = 9
}
}
const val MAX = 239Javaの場合:
int constant = Obj.CONST;
int max = ExampleKt.MAX;
int version = C.VERSION;静的メソッド (Static methods)
前述のように、Kotlinはパッケージレベルの関数を静的メソッドとして表現します。 また、名前付きオブジェクトまたはコンパニオンオブジェクトで定義された関数を @JvmStatic としてアノテートすると、その関数に対して静的メソッドを生成できます。 このアノテーションを使用すると、コンパイラはオブジェクトを囲むクラスの静的メソッドと、オブジェクト自体のインスタンスメソッドの両方を生成します。例:
class C {
companion object {
@JvmStatic fun callStatic() {}
fun callNonStatic() {}
}
}これで、Javaでは callStatic() は静的になりますが、callNonStatic() はそうなりません:
C.callStatic(); // 正常に動作
C.callNonStatic(); // エラー: 静的メソッドではありません
C.Companion.callStatic(); // インスタンスメソッドは残る
C.Companion.callNonStatic(); // 唯一動作する方法名前付きオブジェクトの場合も同様です:
object Obj {
@JvmStatic fun callStatic() {}
fun callNonStatic() {}
}Javaの場合:
Obj.callStatic(); // 正常に動作
Obj.callNonStatic(); // エラー
Obj.INSTANCE.callNonStatic(); // 動作する。シングルトンインスタンスを経由した呼び出し
Obj.INSTANCE.callStatic(); // これも動作するKotlin 1.3以降、@JvmStatic はインターフェースのコンパニオンオブジェクトで定義された関数にも適用されます。 そのような関数は、インターフェース内の静的メソッドにコンパイルされます。インターフェースの静的メソッドはJava 1.8で導入されたため、対応するターゲットを使用するようにしてください。
interface ChatBot {
companion object {
@JvmStatic fun greet(username: String) {
println("Hello, $username")
}
}
}@JvmStatic アノテーションをオブジェクトまたはコンパニオンオブジェクトのプロパティに適用して、そのゲッターおよびセッターメソッドをそのオブジェクトまたはコンパニオンオブジェクトを含むクラスの静的メンバーにすることもできます。
インターフェースのデフォルトメソッド (Default methods in interfaces)
JVMをターゲットにする場合、Kotlinは、別の設定がされていない限り、インターフェースで宣言された関数をデフォルトメソッドにコンパイルします。 これらはインターフェース内の具体的なメソッドであり、Javaクラスは再実装することなく直接継承できます。
以下は、デフォルトメソッドを持つKotlinインターフェースの例です:
interface Robot {
fun move() { println("~walking~") } // Javaインターフェースではデフォルトになります
fun speak(): Unit
}デフォルトの実装は、インターフェースを実装するJavaクラスで利用可能です。
// Javaの実装
public class C3PO implements Robot {
// Robotからの move() の実装は暗黙的に利用可能
@Override
public void speak() {
System.out.println("I beg your pardon, sir");
}
}C3PO c3po = new C3PO();
c3po.move(); // Robotインターフェースからのデフォルト実装
c3po.speak();インターフェースの実装側でデフォルトメソッドをオーバーライドすることもできます。
// Java
public class BB8 implements Robot {
// デフォルトメソッドの独自の実装
@Override
public void move() {
System.out.println("~rolling~");
}
@Override
public void speak() {
System.out.println("Beep-beep");
}
}デフォルトメソッドの互換モード
Kotlinは、インターフェース内の関数をJVMのデフォルトメソッドにコンパイルする方法を制御するための3つのモードを提供しています。 これらのモードは、コンパイラが互換性のためのブリッジ(compatibility bridges)や DefaultImpls クラス内の静的メソッドを生成するかどうかを決定します。
この動作は -jvm-default コンパイラオプションを使用して制御できます。
-jvm-defaultコンパイラオプションは、非推奨となった-Xjvm-defaultオプションを置き換えるものです。
互換モードの詳細:
enable
デフォルトの動作です。 インターフェースにデフォルト実装を生成し、互換ブリッジと DefaultImpls クラスを含めます。 このモードは、以前にコンパイルされたKotlinコードとの互換性を維持します。
no-compatibility
インターフェースにデフォルト実装のみを生成します。 互換ブリッジと DefaultImpls クラスをスキップします。 DefaultImpls クラスに依存するコードと相互作用しない新しいコードベースにこのモードを使用してください。 これは、古いKotlinコードとのバイナリ互換性を壊す可能性があります。
インターフェースの委譲(interface delegation)が使用されている場合、すべてのインターフェースメソッドが委譲されます。
disable
インターフェースのデフォルト実装を無効にします。 互換ブリッジと DefaultImpls クラスのみが生成されます。
可視性 (Visibility)
Kotlinの可視性修飾子は、以下のようにJavaにマッピングされます:
privateメンバーはprivateメンバーにコンパイルされます。privateトップレベル宣言はprivateトップレベル宣言にコンパイルされます。クラス内からアクセスされる場合は、パッケージプライベートなアクセサも含まれます。protectedはprotectedのままです。(Javaは同じパッケージ内の他のクラスからのprotectedメンバーへのアクセスを許可しますが、Kotlinは許可しないため、Javaクラスの方がコードへのアクセス範囲が広くなることに注意してください。)internal宣言はJavaではpublicになります。internalクラスのメンバーは、Javaから誤って使用されるのを防ぎ、Kotlinの規則に従って互いに見えない同じシグネチャを持つメンバーのオーバーロードを可能にするために、名前のマングリング(name mangling)が行われます。publicはpublicのままです。
KClass
KClass 型のパラメータを持つKotlinメソッドを呼び出す必要がある場合があります。 Class から KClass への自動変換はないため、Class<T>.kotlin 拡張プロパティに相当するものを呼び出して手動で行う必要があります。
kotlin.jvm.JvmClassMappingKt.getKotlinClass(MainView.class)@JvmName によるシグネチャ衝突の処理
Kotlinで名前を付けた関数が、バイトコードにおいて異なるJVM名を必要とする場合があります。 最も顕著な例は、型消去(type erasure)によって発生します:
fun List<String>.filterValid(): List<String>
fun List<Int>.filterValid(): List<Int>これら2つの関数は、JVMシグネチャが同じ filterValid(Ljava/util/List;)Ljava/util/List; であるため、並べて定義することはできません。 Kotlinでどうしても同じ名前にしたい場合は、一方(または両方)を @JvmName でアノテートし、引数として別の名前を指定します:
fun List<String>.filterValid(): List<String>
@JvmName("filterValidInt")
fun List<Int>.filterValid(): List<Int>Kotlinからは同じ名前 filterValid でアクセスできますが、Javaからは filterValid と filterValidInt になります。
同じトリックは、プロパティ x と同時に関数 getX() を持たせる必要がある場合にも適用できます:
val x: Int
@JvmName("getX_prop")
get() = 15
fun getX() = 10明示的に実装されたゲッターやセッターを持たないプロパティに対して、生成されるアクセサメソッドの名前を変更するには、@get:JvmName および @set:JvmName を使用できます:
@get:JvmName("x")
@set:JvmName("changeX")
var x: Int = 23オーバーロードの生成 (Overloads generation)
通常、デフォルトの引数値を持つKotlin関数を作成すると、Javaからはすべての引数が存在するフルシグネチャとしてのみ見えます。Javaの呼び出し元に複数のオーバーロードを公開したい場合は、@JvmOverloads アノテーションを使用できます。
このアノテーションは、コンストラクタや静的メソッドなどにも機能します。インターフェースで定義されたメソッドを含む抽象メソッドには使用できません。
class Circle @JvmOverloads constructor(centerX: Int, centerY: Int, radius: Double = 1.0) {
@JvmOverloads fun draw(label: String, lineWidth: Int = 1, color: String = "red") { /*...*/ }
}デフォルト値を持つ各パラメータに対して、このパラメータと、パラメータリスト内のその右側にあるすべてのパラメータを削除した追加のオーバーロードが1つ生成されます。この例では、以下が生成されます:
// コンストラクタ:
Circle(int centerX, int centerY, double radius)
Circle(int centerX, int centerY)
// メソッド
void draw(String label, int lineWidth, String color) { }
void draw(String label, int lineWidth) { }
void draw(String label) { }セカンダリコンストラクタで説明されているように、クラスのすべてのコンストラクタパラメータにデフォルト値がある場合、そのクラスに対して引数なしのパブリックコンストラクタが生成されます。これは、@JvmOverloads アノテーションが指定されていない場合でも機能します。
検査例外 (Checked exceptions)
Kotlinには検査例外がありません。 そのため、通常、Kotlin関数のJavaシグネチャはスローされる例外を宣言しません。 したがって、次のようなKotlinの関数があるとします:
// example.kt
package demo
fun writeToFile() {
/*...*/
throw IOException()
}これをJavaから呼び出して例外をキャッチしようとすると:
// Java
try {
demo.Example.writeToFile();
} catch (IOException e) {
// エラー: writeToFile() は throws リストに IOException を宣言していません
// ...
}writeToFile() が IOException を宣言していないため、Javaコンパイラからエラーメッセージが表示されます。 この問題を回避するには、Kotlinで @Throws アノテーションを使用します:
@Throws(IOException::class)
fun writeToFile() {
/*...*/
throw IOException()
}Null安全性 (Null-safety)
JavaからKotlin関数を呼び出す際、非Null(non-nullable)パラメータとして null を渡すことを防ぐものはありません。 そのため、Kotlinは非Nullを期待するすべてのパブリック関数に対してランタイムチェックを生成します。 これにより、Javaコードで即座に NullPointerException が発生するようになります。
ジェネリクスの変異 (Variant generics)
Kotlinクラスが宣言区変異(declaration-site variance)を使用している場合、それらが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(Derived())) と書けますが、Javaではクラス Box がそのパラメータ T に対して 不変(invariant)であり、したがって Box<Derived> は Box<Base> のサブタイプではないため、それが不可能であることです。 これをJavaで動作させるには、unboxBase を次のように定義する必要があります:
Base unboxBase(Box<? extends Base> box) { ... }この宣言は、Javaの ワイルドカード型(wildcards types: ? extends Base)を使用して、使用区変異を通じて宣言区変異をエミュレートしています。
Kotlin APIをJavaで動作させるために、共変(covariant)に定義された Box(または反変(contravariant)に定義された Foo)が パラメータとして 現れるとき、コンパイラは Box<Super> を Box<? extends Super>(または Foo<? super Bar>)として生成します。戻り値の場合はワイルドカードは生成されません。そうしないとJavaクライアントがそれらを処理しなければならなくなるためです(そしてそれは一般的なJavaのコーディングスタイルに反します)。したがって、この例の関数は実際には次のように翻訳されます:
// 戻り値の型 - ワイルドカードなし
Box<Derived> boxDerived(Derived value) { ... }
// パラメータ - ワイルドカードあり
Base unboxBase(Box<? extends Base> box) { ... }引数の型が final である場合、通常ワイルドカードを生成する意味がないため、
Box<String>はどの位置にあっても常にBox<String>になります。
デフォルトでワイルドカードが生成されない場所にワイルドカードが必要な場合は、@JvmWildcard アノテーションを使用します:
fun boxDerived(value: Derived): Box<@JvmWildcard Derived> = Box(value)
// 次のように翻訳されます
// Box<? extends Derived> boxDerived(Derived value) { ... }逆に、ワイルドカードが生成される場所でワイルドカードが不要な場合は、@JvmSuppressWildcards を使用します:
fun unboxBase(box: Box<@JvmSuppressWildcards Base>): Base = box.value
// 次のように翻訳されます
// Base unboxBase(Box<Base> box) { ... }
@JvmSuppressWildcardsは個別の型引数だけでなく、関数やクラスなどの宣言全体に使用でき、その中のすべてのワイルドカードを抑制できます。
Nothing型の翻訳
Nothing 型は、Javaに自然な対応物がないため特殊です。実際、java.lang.Void を含むすべてのJavaの参照型は値として null を受け入れますが、Nothing はそれさえ受け入れません。したがって、この型をJavaの世界で正確に表現することはできません。これが、Kotlinが Nothing 型の引数が使用される場所に raw 型を生成する理由です:
fun emptyList(): List<Nothing> = listOf()
// 次のように翻訳されます
// List emptyList() { ... }インライン値クラス (Inline value classes)
JavaコードをKotlinのインライン値クラス(inline value classes)とスムーズに連携させたい場合は、@JvmExposeBoxed アノテーションまたは -Xjvm-expose-boxed コンパイラオプションを使用できます。これらのアプローチにより、KotlinはJavaとの相互運用のために必要なボックス化された表現(boxed representations)を生成します。
デフォルトでは、Kotlinはインライン値クラスを、Javaからはアクセスできないことが多い ボックス化されていない表現(unboxed representations)を使用するようにコンパイルします。 例えば、Javaから MyInt クラスのコンストラクタを呼び出すことはできません:
@JvmInline
value class MyInt(val value: Int)そのため、次のJavaコードは失敗します:
MyInt input = new MyInt(5);@JvmExposeBoxed アノテーションを使用すると、KotlinはJavaから直接呼び出すことができるパブリックコンストラクタを生成します。 このアノテーションを以下のレベルで適用して、Javaに公開するものをきめ細かく制御できます:
- クラス
- コンストラクタ
- 関数
コードで @JvmExposeBoxed アノテーションを使用する前に、@OptIn(ExperimentalStdlibApi::class) を使用してオプトインする必要があります。 例:
@OptIn(ExperimentalStdlibApi::class)
@JvmExposeBoxed
@JvmInline
value class MyInt(val value: Int)
@OptIn(ExperimentalStdlibApi::class)
@JvmExposeBoxed
fun MyInt.timesTwoBoxed(): MyInt = MyInt(this.value * 2)これらのアノテーションにより、Kotlinは MyInt クラスに対してJavaからアクセス可能なコンストラクタを生成し、さらに値クラスのボックス化された形式を使用する拡張関数のバリアントも生成します。これにより、次のJavaコードが正常に実行されます:
MyInt input = new MyInt(5);
MyInt output = ExampleKt.timesTwoBoxed(input);この動作をモジュール内のすべてのインライン値クラスおよびそれらを使用する関数に適用するには、-Xjvm-expose-boxed オプションを使用してコンパイルします。 このオプションを使用してコンパイルすることは、モジュール内のすべての宣言に @JvmExposeBoxed アノテーションが付いているのと同じ効果があります。
継承された関数
@JvmExposeBoxed アノテーションは、継承された関数に対して自動的にボックス化された表現を生成しません。
継承された関数に対して必要な表現を生成するには、実装クラスまたは拡張クラスでその関数をオーバーライドしてください:
interface IdTransformer {
fun transformId(rawId: UInt): UInt = rawId
}
// transformId() 関数に対してボックス化された表現を生成しません
@OptIn(ExperimentalStdlibApi::class)
@JvmExposeBoxed
class LightweightTransformer : IdTransformer
// transformId() 関数に対してボックス化された表現を生成します
@OptIn(ExperimentalStdlibApi::class)
@JvmExposeBoxed
class DefaultTransformer : IdTransformer {
override fun transformId(rawId: UInt): UInt = super.transformId(rawId)
}Kotlinでの継承の仕組みや、super キーワードを使用してスーパークラスの実装を呼び出す方法については、継承を参照してください。
