メインコンテンツまでスキップ

JavaからのKotlinの呼び出し

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

プロパティ

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

  • getterメソッド。名前はgetプレフィックスを付加して計算されます。
  • setterメソッド。名前はsetプレフィックスを付加して計算されます(varプロパティのみ)。
  • privateフィールド。プロパティ名と同じ名前です(backing fieldを持つプロパティのみ)。

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

private String firstName;

public String getFirstName() {
return firstName;
}

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

プロパティの名前がisで始まる場合、別の名前マッピング規則が使用されます。getterの名前はプロパティ名と同じになり、setterの名前はissetに置き換えることで取得されます。 例えば、プロパティisOpenの場合、getterはisOpen()と呼ばれ、setterはsetOpen()と呼ばれます。 この規則は、Booleanだけでなく、任意の型のプロパティに適用されます。

パッケージレベルの関数

パッケージorg.example内のファイルapp.ktで宣言されたすべての関数とプロパティ(extension functionを含む)は、org.example.AppKtという名前のJavaクラスのstatic methodにコンパイルされます。

// app.kt
package org.example

class Util

fun getTime() { /*...*/ }

// Java
new org.example.Util();
org.example.AppKt.getTime();

生成されたJavaクラスにカスタム名を付けるには、@JvmName annotationを使用します。

@file:JvmName("DemoUtils")

package org.example

class Util

fun getTime() { /*...*/ }

// Java
new org.example.Util();
org.example.DemoUtils.getTime();

同じ生成されたJavaクラス名(同じパッケージと同じ名前、または同じ@JvmName annotation)を持つ複数のファイルがあることは、通常はエラーです。 ただし、コンパイラは、指定された名前を持ち、その名前を持つすべてのファイルのすべての宣言を含む単一のJava facade classを生成できます。 このようなfacadeの生成を有効にするには、そのようなすべてのファイルで@JvmMultifileClass annotationを使用します。

// 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のfieldとして公開する必要がある場合は、@JvmField annotationを付けてください。 fieldは、基になるプロパティと同じ可視性を持ちます。プロパティが次の条件を満たす場合、@JvmFieldでannotationを付けることができます。

  • backing fieldがある
  • privateではない
  • openoverride、またはconst modifierがない
  • delegated propertyではない
class User(id: String) {
@JvmField val ID = id
}

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

Late-Initialized propertiesもfieldとして公開されます。 fieldの可視性は、lateinit property setterの可視性と同じになります。

Static fields

named objectまたはcompanion objectで宣言されたKotlinのプロパティは、そのnamed objectまたはcompanion objectを含むクラスにstatic backing fieldを持ちます。

通常、これらのfieldはprivateですが、次のいずれかの方法で公開できます。

  • @JvmField annotation
  • lateinit modifier
  • const modifier

このようなプロパティに@JvmFieldでannotationを付けると、プロパティ自体と同じ可視性を持つstatic fieldになります。

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

objectまたはcompanion objectのlate-initialized propertyには、property setterと同じ可視性を持つstatic backing fieldがあります。

object Singleton {
lateinit var provider: Provider
}

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

constとして宣言されたプロパティ(クラス内およびトップレベル)は、Javaのstatic fieldに変換されます。

// file example.kt

object Obj {
const val CONST = 1
}

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

const val MAX = 239

In Java:


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

Static methods

上記のように、Kotlinはパッケージレベルの関数をstatic methodとして表します。 Kotlinは、named objectまたはcompanion objectで定義された関数に@JvmStatic annotationを付けると、static methodを生成することもできます。 このannotationを使用すると、コンパイラはobjectの囲みクラスのstatic methodと、object自体のinstance methodの両方を生成します。 例:

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

これで、callStatic()はJavaではstaticですが、callNonStatic()はstaticではありません。


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

named objectも同様です。

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

In 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はインターフェースのcompanion objectで定義された関数にも適用されます。 このような関数は、インターフェースのstatic methodにコンパイルされます。 インターフェースのstatic methodはJava 1.8で導入されたため、対応するターゲットを使用するようにしてください。

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

@JvmStatic annotationは、objectまたはcompanion objectのプロパティにも適用でき、そのgetterおよびsetter methodを、そのobjectまたはcompanion objectを含むクラスのstatic memberにすることができます。

インターフェースのDefault methods

注記

Default methodsは、ターゲットJVM 1.8以降でのみ使用できます。

JDK 1.8以降、Javaのインターフェースにはdefault methodsを含めることができます。 Kotlinインターフェースの抽象的でないすべてのmemberを、それらを実装するJavaクラスのdefaultにするには、-Xjvm-default=allコンパイラオプションを使用してKotlinコードをコンパイルします。

Default methodを持つKotlinインターフェースの例を次に示します。

// compile with -Xjvm-default=all

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

defaultの実装は、インターフェースを実装する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");
}
}
C3PO c3po = new C3PO();
c3po.move(); // default implementation from the Robot interface
c3po.speak();

インターフェースの実装は、default methodをoverrideできます。

//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 1.4より前は、default methodを生成するために、これらのmethodに@JvmDefault annotationを使用できました。 1.4以降で-Xjvm-default=allでコンパイルすると、通常はインターフェースの抽象的でないすべてのmethodに@JvmDefaultでannotationを付け、-Xjvm-default=enableでコンパイルした場合と同様に機能します。 ただし、それらの動作が異なる場合があります。 Kotlin 1.4でのdefault method生成の変更に関する詳細情報は、Kotlinブログのこの投稿に記載されています。

Default methodsの互換性モード

-Xjvm-default=allオプションなしでコンパイルされたKotlinインターフェースを使用するクライアントがある場合、このオプションでコンパイルされたコードとのバイナリ互換性がない可能性があります。 このようなクライアントとの互換性を損なわないようにするには、-Xjvm-default=allモードを使用し、@JvmDefaultWithCompatibility annotationでインターフェースをマークします。 これにより、このannotationをパブリックAPIのすべてのインターフェースに一度だけ追加でき、新しいパブリックでないコードにannotationを使用する必要はありません。

注記

Kotlin 1.6.20以降、デフォルトモード(-Xjvm-default=disableコンパイラオプション)で、-Xjvm-default=allまたは-Xjvm-default=all-compatibilityモードでコンパイルされたモジュールに対してモジュールをコンパイルできます。

互換性モードの詳細:

disable

デフォルトの動作。 JVM default methodを生成せず、@JvmDefault annotationの使用を禁止します。

all

モジュール内のボディを持つすべてのインターフェース宣言に対して、JVM default methodを生成します。 disableモードでデフォルトで生成される、ボディを持つインターフェース宣言のDefaultImplsスタブを生成しません。

インターフェースがdisableモードでコンパイルされたインターフェースからボディを持つmethodを継承し、それをoverrideしない場合、DefaultImplsスタブが生成されます。

一部のクライアントコードがDefaultImplsクラスの存在に依存している場合、バイナリ互換性が損なわれます

注記

インターフェースの委譲が使用されている場合、すべてのインターフェースmethodが委譲されます。 唯一の例外は、非推奨の@JvmDefault annotationでannotationが付けられたmethodです。

all-compatibility

allモードに加えて、DefaultImplsクラスに互換性スタブを生成します。 互換性スタブは、ライブラリおよびランタイムの作成者が、以前のライブラリバージョンに対してコンパイルされた既存のクライアントに対して、下位バイナリ互換性を維持するのに役立ちます。 allモードとall-compatibilityモードは、ライブラリの再コンパイル後にクライアントが使用するライブラリABIサーフェスを変更します。 その意味で、クライアントは以前のライブラリバージョンと互換性がない可能性があります。 これは通常、適切なライブラリのバージョン管理(たとえば、SemVerのメジャーバージョンの増加)が必要であることを意味します。

コンパイラは、@Deprecated annotationを使用してDefaultImplsのすべてのmemberを生成します。コンパイラは互換性の目的でのみ生成するため、Javaコードでこれらのmemberを使用しないでください。

allモードまたはall-compatibilityモードでコンパイルされたKotlinインターフェースからの継承の場合、DefaultImpls互換性スタブは、標準のJVMランタイム解決セマンティクスを使用してインターフェースのdefault methodを呼び出します。

ジェネリックインターフェースを継承するクラスに対して追加の互換性チェックを実行します。disableモードでは、特殊化されたシグネチャを持つ追加の暗黙的なmethodが生成される場合がありました。 disableモードとは異なり、そのようなmethodを明示的にoverrideせず、クラスに@JvmDefaultWithoutCompatibilityでannotationを付けない場合、コンパイラはエラーを報告します(詳細については、このYouTrackの問題を参照してください)。

可視性

Kotlinの可視性modifierは、次の方法でJavaにマップされます。

  • private memberはprivate memberにコンパイルされます
  • privateトップレベル宣言はprivateトップレベル宣言にコンパイルされます。パッケージプライベートアクセサーも、クラス内からアクセスされる場合は含まれます。
  • protectedprotectedのままです(Javaは同じパッケージ内の他のクラスからのprotected memberへのアクセスを許可しますが、Kotlinは許可しないため、Javaクラスはコードへのより広範なアクセス権を持つことに注意してください)
  • internal宣言はJavaではpublicになります。internalクラスのmemberは、Javaから誤って使用することを困難にし、Kotlinのルールに従って互いに見えない同じシグネチャのmemberのオーバーロードを許可するために、name manglingされます。
  • publicpublicのままです

KClass

KClass型のパラメータを持つKotlin methodを呼び出す必要がある場合があります。 ClassからKClassへの自動変換はないため、Class<T>.kotlin extension propertyと同等のものを呼び出すことで手動で行う必要があります。

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でannotationを付け、引数として別の名前を指定できます。

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

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

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

プロパティxと関数getX()を並べて持つ必要がある場合にも、同じトリックが適用されます。

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

fun getX() = 10

明示的に実装されたgetterとsetterがないプロパティに対して、生成されたアクセサーmethodの名前を変更するには、@get:JvmNameおよび@set:JvmNameを使用できます。

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

Overloads generation

通常、デフォルトのパラメータ値を持つKotlin関数を作成した場合、Javaではすべてのパラメータが存在する完全なシグネチャとしてのみ表示されます。 Javaの呼び出し元に複数のoverloadを公開する場合は、@JvmOverloads annotationを使用できます。

annotationは、コンストラクタ、static methodなどにも使用できます。 インターフェースで定義されたmethodを含む、抽象methodでは使用できません。

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

デフォルト値を持つパラメータごとに、パラメータリストからこのパラメータと右側のすべてのパラメータが削除された追加のoverloadが1つ生成されます。 この例では、以下が生成されます。

// 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) { }

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

Checked exceptions

Kotlinにはchecked exceptionsはありません。 したがって、通常、Kotlin関数のJavaシグネチャは、スローされたexceptionsを宣言しません。 したがって、Kotlinに次のような関数がある場合:

// example.kt
package demo

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

Javaから呼び出して例外をキャッチする場合:


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

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

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

Null-safety

JavaからKotlin関数を呼び出す場合、nullableでないパラメータとしてnullを渡すことを妨げるものはありません。 そのため、Kotlinはnullableでないものを期待するすべてのパブリック関数に対してランタイムチェックを生成します。 これにより、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では不可能です。 なぜなら、JavaではクラスBoxはパラメータT不変であり、したがってBox<Derived>Box<Base>のサブタイプではないためです。 Javaでこれを機能させるには、unboxBaseを次のように定義する必要があります。

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

この宣言は、Javaのワイルドカード型? extends Base)を使用して、use-site varianceを介してdeclaration-site varianceをエミュレートします。なぜなら、Javaにはそれしかありません。

Kotlin APIをJavaで機能させるために、コンパイラは共変的に定義されたBoxに対してBox<Super>Box<? extends Super>として生成します。 (または、反変的に定義されたFooの場合はFoo<? super Bar>パラメータとして表示される場合。 戻り値の場合、ワイルドカードは生成されません。そうしないと、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 annotationを使用します。

fun boxDerived(value: Derived): Box<@JvmWildcard Derived> = Box(value)
// is translated to
// Box<? extends Derived> 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型の引数が使用される場所にraw typeを生成します。

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