Skip to content

JavaからKotlinを呼び出す

KotlinコードはJavaから簡単に呼び出すことができます。 例えば、KotlinクラスのインスタンスはJavaメソッド内でシームレスに作成し、操作できます。 しかし、JavaにKotlinコードを統合する際には、JavaとKotlinの間に注意が必要な特定の相違点があります。 このページでは、KotlinコードとJavaクライアントとの相互運用を調整する方法について説明します。

プロパティ

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

  • ゲッターメソッド。名前はgetプレフィックスを前置して計算されます。
  • セッターメソッド。名前はsetプレフィックスを前置して計算されます(varプロパティの場合のみ)。
  • プライベートフィールド。プロパティ名と同じ名前です(バッキングフィールドを持つプロパティの場合のみ)。

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

java
private String firstName;

public String getFirstName() {
    return firstName;
}

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

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

パッケージレベルの関数

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

kotlin
// app.kt
package org.example

class Util

fun getTime() { /*...*/ }
java
// Java
new org.example.Util();
org.example.AppKt.getTime();

生成されるJavaクラスにカスタム名を指定するには、@JvmNameアノテーションを使用します。

kotlin
@file:JvmName("DemoUtils")

package org.example

class Util

fun getTime() { /*...*/ }
java
// Java
new org.example.Util();
org.example.DemoUtils.getTime();

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

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

package org.example

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

package org.example

fun getDate() { /*...*/ }
java
// Java
org.example.Utils.getTime();
org.example.Utils.getDate();

インスタンスフィールド

KotlinのプロパティをJavaのフィールドとして公開する必要がある場合、@JvmFieldアノテーションを付与します。 フィールドは、基となるプロパティと同じ可視性を持ちます。プロパティに@JvmFieldを付与できるのは、以下の条件を満たす場合です。

  • バッキングフィールドを持つ
  • プライベートではない
  • openoverride、またはconst修飾子を持たない
  • 委譲プロパティではない
kotlin
class User(id: String) {
    @JvmField val ID = id
}
java

// Java
class JavaClient {
    public String getID(User user) {
        return user.ID;
    }
}

遅延初期化プロパティもフィールドとして公開されます。 フィールドの可視性は、lateinitプロパティのセッターの可視性と同じです。

静的フィールド

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

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

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

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

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

オブジェクトまたはコンパニオンオブジェクト内の遅延初期化プロパティは、プロパティセッターと同じ可視性を持つ静的バッキングフィールドを持ちます。

kotlin
object Singleton {
    lateinit var provider: Provider
}
java

// Java
Singleton.provider = new Provider();
// public static non-final field in Singleton class

constとして宣言されたプロパティ(クラス内およびトップレベルの両方)は、Javaでは静的フィールドに変換されます。

kotlin
// file example.kt

object Obj {
    const val CONST = 1
}

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

const val MAX = 239

Javaでは:

java

int constant = Obj.CONST;
int max = ExampleKt.MAX;
int version = C.VERSION;

静的メソッド

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

kotlin
class C {
    companion object {
        @JvmStatic fun callStatic() {}
        fun callNonStatic() {}
    }
}

この場合、callStatic()はJavaで静的ですが、callNonStatic()はそうではありません。

java

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

同様に、名前付きオブジェクトの場合:

kotlin
object Obj {
    @JvmStatic fun callStatic() {}
    fun callNonStatic() {}
}

Javaでは:

java

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

Kotlin 1.3以降、@JvmStaticはインターフェースのコンパニオンオブジェクトで定義された関数にも適用されます。 このような関数は、インターフェース内の静的メソッドにコンパイルされます。インターフェース内の静的メソッドはJava 1.8で導入されたため、対応するターゲットを使用するようにしてください。

kotlin
interface ChatBot {
    companion object {
        @JvmStatic fun greet(username: String) {
            println("Hello, $username")
        }
    }
}

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

インターフェースのデフォルトメソッド

JVMをターゲットとする場合、Kotlinはインターフェースで宣言された関数を、別途設定されていない限りデフォルトメソッドにコンパイルします。 これらは、Javaクラスが再実装なしで直接継承できるインターフェース内の具象メソッドです。

Kotlinインターフェースのデフォルトメソッドの例を以下に示します。

kotlin
interface Robot {
    fun move() { println("~walking~") }  // will be default in the Java interface
    fun speak(): Unit
}

デフォルトの実装は、そのインターフェースを実装するJavaクラスで利用可能です。

java
//Java implementation
public class C3PO implements Robot {
    // move() implementation from Robot is available implicitly
    @Override
    public void speak() {
        System.out.println("I beg your pardon, sir");
    }
}
java
C3PO c3po = new C3PO();
c3po.move(); // default implementation from the Robot interface
c3po.speak();

インターフェースの実装は、デフォルトメソッドをオーバーライドできます。

java
//Java
public class BB8 implements Robot {
    //own implementation of the default method
    @Override
    public void move() {
        System.out.println("~rolling~");
    }

    @Override
    public void speak() {
        System.out.println("Beep-beep");
    }
}

デフォルトメソッドの互換性モード

Kotlinは、インターフェース内の関数がJVMのデフォルトメソッドにコンパイルされる方法を制御するための3つのモードを提供します。 これらのモードは、コンパイラが互換性ブリッジとDefaultImplsクラス内の静的メソッドを生成するかどうかを決定します。

この動作は、-jvm-defaultコンパイラオプションを使用して制御できます。

-jvm-defaultコンパイラオプションは、非推奨の-Xjvm-defaultオプションを置き換えるものです。

互換性モードについて詳しくはこちら:

enable

デフォルトの動作。 インターフェースにデフォルト実装を生成し、互換性ブリッジとDefaultImplsクラスを含みます。 このモードは、古いコンパイル済みKotlinコードとの互換性を維持します。

no-compatibility

インターフェースにデフォルト実装のみを生成します。 互換性ブリッジとDefaultImplsクラスをスキップします。 DefaultImplsクラスに依存するコードと相互作用しない新しいコードベースにこのモードを使用してください。 これにより、古いKotlinコードとのバイナリ互換性が損なわれる可能性があります。

インターフェース委譲が使用されている場合、すべてのインターフェースメソッドが委譲されます。

disable

インターフェースにおけるデフォルト実装を無効にします。 互換性ブリッジとDefaultImplsクラスのみが生成されます。

可視性

Kotlinの可視性修飾子は、Javaに以下のようにマッピングされます。

  • privateメンバーはprivateメンバーにコンパイルされます。
  • privateトップレベル宣言はprivateトップレベル宣言にコンパイルされます。クラス内からアクセスされる場合、パッケージプライベートアクセサーも含まれます。
  • protectedprotectedのままです。(Javaは同じパッケージ内の他のクラスからプロテクテッドメンバーへのアクセスを許可しますが、Kotlinはそうではないため、Javaクラスはコードに対してより広いアクセス権を持ちます。)
  • internal宣言はJavaではpublicになります。internalクラスのメンバーは名前マングリング(名前の改変)を受けます。これは、Javaから誤って使用することを困難にし、Kotlinのルールに従って互いに見えない同じシグネチャを持つメンバーのオーバーロードを許可するためです。
  • publicpublicのままです。

KClass

KotlinメソッドをKClass型のパラメータで呼び出す必要がある場合があります。 ClassからKClassへの自動変換はないため、Class<T>.kotlin拡張プロパティに相当するものを呼び出すことで手動で行う必要があります。

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

@JvmNameによるシグネチャ競合の処理

Kotlinでは名前付き関数が存在し、バイトコードでは異なるJVM名が必要となる場合があります。 最も顕著な例は、型消去によって発生します。

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

これらの2つの関数は、JVMシグネチャが同じであるため、並べて定義することはできません:filterValid(Ljava/util/List;)Ljava/util/List;。 Kotlinで同じ名前を持たせたい場合、一方(または両方)に@JvmNameアノテーションを付与し、引数として異なる名前を指定できます。

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

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

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

プロパティxと関数getX()を同時に持つ必要がある場合も、同じ手法が適用されます。

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

fun getX() = 10

明示的に実装されていないゲッターとセッターを持つプロパティの生成されるアクセサーメソッドの名前を変更するには、@get:JvmName@set:JvmNameを使用できます。

kotlin
@get:JvmName("x")
@set:JvmName("changeX")
var x: Int = 23

オーバーロードの生成

通常、デフォルト引数を持つKotlin関数を記述した場合、Javaではすべての引数が存在する完全なシグネチャとしてのみ可視化されます。Javaの呼び出し元に複数のオーバーロードを公開したい場合は、@JvmOverloadsアノテーションを使用できます。

このアノテーションは、コンストラクタ、静的メソッドなどにも機能します。インターフェースで定義されたメソッドを含む抽象メソッドには使用できません。

kotlin
class Circle @JvmOverloads constructor(centerX: Int, centerY: Int, radius: Double = 1.0) {
    @JvmOverloads fun draw(label: String, lineWidth: Int = 1, color: String = "red") { /*...*/ }
}

デフォルト値を持つパラメータごとに、そのパラメータとそれより右にあるすべてのパラメータが引数リストから削除された、追加のオーバーロードが1つ生成されます。この例では、以下が生成されます。

java
// Constructors:
Circle(int centerX, int centerY, double radius)
Circle(int centerX, int centerY)

// Methods
void draw(String label, int lineWidth, String color) { }
void draw(String label, int lineWidth) { }
void draw(String label) { }

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

チェック例外

Kotlinにはチェック例外がありません。 したがって、通常、Kotlin関数のJavaシグネチャはスローされる例外を宣言しません。 そのため、Kotlinに以下のような関数がある場合:

kotlin
// example.kt
package demo

fun writeToFile() {
    /*...*/
    throw IOException()
}

そしてJavaからそれを呼び出して例外をキャッチしたい場合:

java

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

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

kotlin
@Throws(IOException::class)
fun writeToFile() {
    /*...*/
    throw IOException()
}

Null安全性

JavaからKotlin関数を呼び出す際、非null許容パラメータとしてnullを渡すことを誰も妨げません。 そのため、Kotlinは非nullを期待するすべてのpublic関数に対して実行時チェックを生成します。 これにより、Javaコードで即座にNullPointerExceptionが発生します。

変性ジェネリクス

Kotlinクラスが宣言サイト変性を利用する場合、Javaコードからその使用がどのように見られるかには2つの選択肢があります。例えば、以下のクラスとそのクラスを使用する2つの関数があるとします。

kotlin
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に翻訳する素朴な方法は次のようになります。

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

問題は、KotlinではunboxBase(boxDerived(Derived()))と記述できるのに対し、JavaではBoxクラスがそのパラメータTに対して不変であり、Box<Derived>Box<Base>のサブタイプではないため、それが不可能であることです。 Javaでこれを機能させるには、unboxBaseを次のように定義する必要があります。

java
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のコーディングスタイルに反するため)です。 したがって、私たちの例の関数は実際には次のように翻訳されます。

java

// return type - no wildcards
Box<Derived> boxDerived(Derived value) { ... }
 
// parameter - wildcards 
Base unboxBase(Box<? extends Base> box) { ... }

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

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

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

反対に、ワイルドカードが生成される場所でワイルドカードが不要な場合は、@JvmSuppressWildcardsを使用します。

kotlin
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型の引数が使用される場合に、raw型を生成します。

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

インライン値クラス

JavaコードをKotlinのインライン値クラスとスムーズに連携させたい場合、@JvmExposeBoxedアノテーションまたは-Xjvm-expose-boxedコンパイラオプションを使用できます。これらのアプローチにより、KotlinはJavaの相互運用に必要なボックス化された表現を確実に生成します。

デフォルトでは、Kotlinはインライン値クラスをアンボックス化された表現を使用するようにコンパイルします。これはJavaからアクセスできないことが多いです。 例えば、JavaからMyIntクラスのコンストラクタを呼び出すことはできません。

kotlin
@JvmInline
value class MyInt(val value: Int)

したがって、以下のJavaコードは失敗します。

java
MyInt input = new MyInt(5);

@JvmExposeBoxedアノテーションを使用すると、KotlinがJavaから直接呼び出せるpublicコンストラクタを生成するようにできます。 Javaに公開する内容を細かく制御するために、このアノテーションを以下のレベルで適用できます。

  • クラス
  • コンストラクタ
  • 関数

コードで@JvmExposeBoxedアノテーションを使用する前に、@OptIn(ExperimentalStdlibApi::class)を使用してオプトインする必要があります。 例えば:

kotlin
@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コードが正常に実行されます。

java
MyInt input = new MyInt(5);
MyInt output = ExampleKt.timesTwoBoxed(input);

この動作をモジュール内のすべてのインライン値クラスとそれらを使用する関数に適用するには、-Xjvm-expose-boxedオプションを使用してコンパイルします。 このオプションでコンパイルすると、モジュール内のすべての宣言が@JvmExposeBoxedアノテーションを持つかのような効果が得られます。

継承された関数

@JvmExposeBoxedアノテーションは、継承された関数のボックス化された表現を自動的に生成しません。

継承された関数に必要な表現を生成するには、実装または拡張するクラスでそれをオーバーライドします。

kotlin
interface IdTransformer {
    fun transformId(rawId: UInt): UInt = rawId
}

// Doesn't generate a boxed representation for the transformId() function
@OptIn(ExperimentalStdlibApi::class)
@JvmExposeBoxed
class LightweightTransformer : IdTransformer

// Generates a boxed representation for the transformId() function
@OptIn(ExperimentalStdlibApi::class)
@JvmExposeBoxed
class DefaultTransformer : IdTransformer {
    override fun transformId(rawId: UInt): UInt = super.transformId(rawId)
}

Kotlinにおける継承の仕組みと、superキーワードを使用してスーパークラスの実装を呼び出す方法については、継承を参照してください。