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の名前はis
をset
に置き換えることで取得されます。
例えば、プロパティ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ではない
open
、override
、または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
annotationlateinit
modifierconst
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
トップレベル宣言にコンパイルされます。パッケージプライベートアクセサーも、クラス内からアクセスされる場合は含まれます。protected
はprotected
のままです(Javaは同じパッケージ内の他のクラスからのprotected memberへのアクセスを許可しますが、Kotlinは許可しないため、Javaクラスはコードへのより広範なアクセス権を持つことに注意してください)internal
宣言はJavaではpublic
になります。internal
クラスのmemberは、Javaから誤って使用することを困難にし、Kotlinのルールに従って互いに見えない同じシグネチャのmemberのオーバーロードを許可するために、name manglingされます。public
はpublic
のままです
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からはfilterValid
とfilterValidInt
になります。
プロパティ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() { ... }