KotlinにおけるJavaとの相互運用性を高めるための工夫
これはKotlin Advent Calendar 2017の6日目の記事です。
はじめに
KotlinはJavaとの相互運用性を重視していますが、JavaとKotlinは言語仕様的に異なる部分があり、Kotlinはその違いを吸収するために様々な工夫をしています。
Jvmアノテーション
JvmStaticアノテーション
KotlinのCompanion Objectで定義したコードをJavaから呼び出すと、Companionを経由することになります。Companion経由であっても動作上の問題はありませんが、出来れば普通にJavaで定義した場合と同様な呼び出し方をしたいですよね?そういった場合はJvmStaticアノテーションを付けることで、Staticメソッドとして定義されているかのように呼び出すことができます。
// Kotlin class JvmStaticSampleForCompanionObject { companion object { @JvmStatic fun doSomething() { /* 省略 */ } } }
// Java JvmStaticSampleForCompanionObject.Companion.doSomething(); // JvmStaticアノテーションなし JvmStaticSampleForCompanionObject.doSomething(); // JvmStaticアノテーションあり
また、Objectで定義したクラスのメソッドにJvmStaticアノテーションをつけた場合も同様の挙動になります。
// Kotlin object JvmStaticSampleForObject { @JvmStatic fun doSomething() { /* 省略 */ } }
// Java JvmStaticSampleForObject.INSTANCE.doSomething(); // JvmStaticアノテーションなし JvmStaticSampleForObject.doSomething(); // JvmStaticアノテーションあり
単純に呼び出し方の問題ならCompanion経由でも動作上はまったく問題ありません。しかし、Javaライブラリの中にはStaticメソッドとして定義されていることを期待して実装されているものもあり、そういったライブラリを使う場合はJvmStaticアノテーションが有効です。
例えば、AndroidにおけるData Bindingでカスタムバインディングを定義する場合、Staticメソッドとして定義されていることを期待しているため、Kotlinでカスタムバインディングを実装する場合はJvmStaticアノテーションが必要になります。具体例に関しては、Android Data Binding Tipsをご覧ください。
JvmFieldアノテーション
Kotlinでプロパティとして定義したものをJavaから呼び出す場合は自動生成されたGetter/Setterを経由してアクセスすることになりますが、このプロパティにJvmFieldアノテーションを付与することでJavaのフィールドとして定義されているかのように呼び出すことができます。
// Kotlin class JvmFieldSample { @JvmField val foo = 0 }
// Java JvmFieldSample jvmFieldSample = new JvmFieldSample(); jvmFieldSample.getFoo(); // JvmFieldアノテーションなし jvmFieldSample.foo; // JvmFieldアノテーションあり
JvmOverloadsアノテーション
Kotlinはデフォルト引数をサポートしており、メソッドのオーバーロードを簡潔に記述することができますが、そのままではJavaから呼び出した場合にオーバーロードされていることになりません。このような場合にはJvmOverloadsアノテーションをつけることでオーバーロードされているかのように呼び出すことができます。
// Kotlin class JvmOverloadsSample @JvmOverloads constructor(val foo: Int, val bar: Int = 0)
// Java new JvmOverloadsSample(0); new JvmOverloadsSample(0, 0);
その他
Jvmアノテーションは今回紹介した以外にもいくつかあります。
これらに関してはこの辺に解説があるので、興味がある方は覗いてみてください。
Platform Type
KotlinはJavaとの相互運用性を重視しており、Kotlin/Javaが混在するプロジェクトではJavaからKotlinのコードを呼び出すこともあると思います。ここで疑問なのが、Kotlinは言語仕様としてNonNullとNullableを明確に区別している点です。Javaから呼び出す以上はこれらを区別することは不可能ですが、JavaからKotlinのコードを呼び出すこと自体は問題なくできます。このような型の扱い方の違いを吸収するためにKotlinはPlatform Typeという特殊な型を持っています。
具体例を交えつつ説明すると、以下のようなJavaクラスを定義し、それをKotlinから呼び出す場合を考えてみます。
// Java public class PlatformTypeSample { public static Integer getInteger() { return 0; } }
// Kotlin val int = PlatformTypeSample.getInteger()
さて、上記のintはどんな型として推論されるでしょうか? Intでしょうか、Int?でしょうか。
答えはそのどちらでもなく、Int!になります。
この最後に!の付いた型がPlatform Typeで、Intかもしれないし、Int?かもしれない型になります。つまり、実行時にnullが渡された場合は実行時エラーが発生することになります。これではせっかくKotlinで書いているメリットが半減してしまいますが、NonNullやNullableを使うことでPlatform Typeとして推論されることを防ぐことができます。
// Java public class PlatformTypeSample { @NonNull or @Nullable public static Integer getInteger() { return 0; } }
// Kotlin val nonNullInt: Int = PlatformTypeSample.getInteger() // NonNullアノテーション val nullableInt: Int? = PlatformTypeSample.getInteger() // Nullableアノテーション
アノテーションをうまく活用することでJavaと共存しつつも型安全なコードを書くことができます。
Mapped Type
JavaとKotlinは言語仕様が異なるため、以下のようなマッピングが定義されており、相互運用する場合はこれに沿ってマッピングされることになります。
Primitive Type
Java | Kotlin |
---|---|
byte | kotlin.Byte |
short | kotlin.Short |
int | kotlin.Int |
long | kotlin.Long |
char | kotlin.Char |
float | kotlin.Float |
double | kotlin.Double |
boolean | kotlin.Boolean |
Non-Primitive Built-in Class
Java | Kotlin |
---|---|
java.lang.Object | kotlin.Any! |
java.lang.Cloneable | kotlin.Cloneable! |
java.lang.Comparable | kotlin.Comparable! |
java.lang.Enum | kotlin.Enum! |
java.lang.Annotation | kotlin.Annotation! |
java.lang.Deprecated | kotlin.Deprecated! |
java.lang.CharSequence | kotlin.CharSequence! |
java.lang.String | kotlin.String! |
java.lang.Number | kotlin.Number! |
java.lang.Throwable | kotlin.Throwable! |
Javaの参照型は基本的にPlatform Typeにマッピングされるようなので、積極的にNonNullアノテーションをつけておきたいところです。
Boxed Primitive Type
Java | Kotlin |
---|---|
java.lang.Byte | kotlin.Byte? |
java.lang.Short | kotlin.Short? |
java.lang.Integer | kotlin.Int? |
java.lang.Long | kotlin.Long? |
java.lang.Character | kotlin.Char? |
java.lang.Float | kotlin.Float? |
java.lang.Double | kotlin.Double? |
java.lang.Boolean | kotlin.Boolean? |
まとめ
この記事ではKotlinがJavaとの互換性を保つために行っている工夫をいくつか紹介しました。
- Jvmアノテーション
- Platform Type:KotlinからJavaを呼び出した結果として返却される特殊な型
- Mapped Type:Javaの型がKotlinのどの型にマッピングされるかの定義
今回紹介した以外にも公式リファレンスには涙ぐましい?工夫が載っています。興味のある方は以下のページを覗いてみると面白いかもしれません。
それでは、Have a nice Kotlin!