読者です 読者をやめる 読者になる 読者になる

算譜王におれはなる!!!!

偏りはあると思うけど情報技術全般についてマイペースに書くよ。

KotlinとJavaの相互利用性

※下記URLのサイトを参考にしました。英語、または技術的な知識が至らず、内容に誤りが含まれるおそれがありますので、ご了承ください。
※本エントリは参考サイトの翻訳をベースに、加筆・変更を施した構成となっています。

参考サイト http://confluence.jetbrains.net/display/Kotlin/Java+interoperability

KotlinはJavaとの互換性を念頭に置いて設計されました。既存のJavaコードはKotlinから自然に呼べるし、KotlinコードもJavaから同じくらい円滑に使えます。

KotlinからのJavaコード呼び出し

このセクションでは、KotlinからのJavaコード呼び出しについての詳細を説明します。ほとんどの場合、まさにそれを利用するでしょう。

import java.util.*

fun demo(source : List<Int>) {
  val list = ArrayList<Int>()
  // Javaのコレクションでforループは動作する:
  for (item in source)
    list.add(item)
  // 演算子の規則も同様に動作する:
  for (i in 0..source.size() - 1)
    list[i] = source[i] // get と set が呼び出される
}
voidを返すメソッド

Javaのメソッドがvoidを返す場合、Kotlinから呼ばれるときにはUnitを返すでしょう。万一、その戻り値を使う場合は、それはKotlinコンパイラによってコールサイトで割り当てられます。その値は事前に知られている(()である)からです。

Kotlinのキーワードと同じJavaの識別子のエスケープ

Kotlinのキーワードの中には、Javaの識別子として有効なものもあります。in, object, isなどです。あなたのお気に入りのJavaライブラリが、たまたまそれらのワードをメソッド名として使っているかも知れません。この状況でもあなたはそれらのメソッドをKotlinから呼べますが、あなたはバッククオートでそれらの名前をエスケープする必要があります:

foo.`is`(bar)

http://confluence.jetbrains.net/display/Kotlin/Java+interoperability より引用

NULL安全

Javaにおける、いかなる参照もnullとなる可能性があります。そのため、Kotlinから呼ばれる全てのJavaメソッドはnull許容参照を返します(@NotNullが付加されたものを除く)。これは、KotlinがNullPointerExceptionをもたらさないことを保証し続けることを許可します(Kotlinが明示的にNPEを投げたり、Kotlinが呼び出したJavaコード内部で起こらない限りにおいて)。

チェック例外

Kotlinでは、すべて非チェック例外です。これは、コンパイラが例外の捕捉をプログラマに強要しないことを意味します。したがって、チェック例外を宣言しているJavaメソッドを呼び出しても、コンパイラはあなたに何も強要しません。

KotlinにおけるJavaジェネリクス

KotlinのジェネリクスJavaのそれと少し違いがあります(ジェネリクスの記事を見てください)。KotlinへJavaの型をインポートするときに、私たちはいくつかの変換を実行します:

  • Javaワイルドカードは、型投影に変換される
    • Foo<? extends Bar> は Foo<out Bar> になる
    • Foo<? super Bar> は Foo<in Bar> になる
  • Javaのraw型は、スタープロジェクションに変換される
    • List は List<*>, つまり List になる

その他に、Javaジェネリクスは実行時に保持されない、つまりJavaオブジェクトはそのコンストラクタへ渡される実際の型引数についての情報を持ちまわりません。具体的に言うと、new ArrayList() は new ArrayList() と区別がつきません。これは、Javaジェネリクスを考慮するようなinstanceofチェックを不可能にします。KotlinはスタープロジェクトされたJavaジェネリック型のisチェックを可能にします。

if (a is java.util.List<Int>) // エラー: これが本当にIntのListであるかを確認することはできない
// しかし
if (a is java.util.List<*>) // OK: Listの内容については保証しない
不変な配列

Kotlinの配列はJavaと違い不変(invariant)です。これはKotlinが、ArrayをArrayへ割り当てることを許可しないことを意味し、可能性のある実行時障害を防ぎます。ほとんどの場合で、これは大きな妨げにはなりませんが、共変(covariant)な方法で配列を渡す必要が本当にあるなら、明示的にキャストしてください。

Javaプラットフォーム上で配列を表すためにジェネリッククラス Array を使うことは、多くのボクシング/アンボクシング操作につながります。配列はたいてい、パフォーマンスが重大なところで使用されるので、Kotlinにはこの問題の対処が導入され、Arrayクラスとは関係がなく、Javaのプリミティブな配列にコンパイルされるIntArray, DoubleArray, CharArrayなどのクラスが定義されています。

Objectメソッド

Javaの型がKotlinへインポートされるとき、いかなる参照もKotlinで扱えるようにjava.lang.Object型の参照はすべて Any? に変わります。

java.lang.ObjectとAnyの大きな違いは、Anyはいかなるメンバもまったく宣言していないことです。これはKotlinにおける継承のルールのためです。では、toString(), equals()などが必要になったらどうすればいいのでしょうか。

toString()
toString()は、toStringと命名されたインスタンス関数を探して、それを呼び出す拡張関数として宣言されています。toStringがない場合は、typeinfo.typeinfo(this) + "@" + System.identityHashCode(this)となるようなデフォルト値を返します。

プログラマの観点からは、Javaとほとんど変わりません。すべての既存のtoString()の実装は動作するし、あなたのクラスにカスタムtoStringが必要なときは簡単に書けます。

class A() {
  fun toString() : String = "A"
}

http://confluence.jetbrains.net/display/Kotlin/Java+interoperability より引用

equals()
Kotlinにおいて == はguarded call to equals()を表します。左辺の式は、Any?型の1つのパラメートを取り、Booleanを返す equalsと名付けられたメソッドを持つ必要があります。したがって、すべてのJavaオブジェクトはそのままでそれを持っています。一方、toString()のように同様の検索を行う拡張関数もあります。

hashCode()
hashCode()Javaオブジェクトで動作します。

wait()/notify()
Effective Javaの項目69はPrefer concurrency utilities to wait and notify*1を親切に提案しています。したがってAny型の参照ではこれらのメソッドは有効ではなく、Javaオブジェクトでのみ有効です。

getClass()
オブジェクトから型情報を取り出すためには、typeinfo()関数を使用します。getClass()はJavaオブジェクトで有効です。

finalize()
finalize()はtoString()のように正確にオーバライドが可能です。

clone()
clone()はtoString()のようにオーバライド可能ですが、スーパタイプとしてCloneableを指定してください。Effective Javaの項目11:Override clone judiciously*2を忘れないでください。

Javaクラスの継承

高々1つのJavaクラスを(Javaインタフェースは好きなだけ)Kotlinクラスのスーパタイプとできます。そのクラスはスーパタイプリストの最初に置かなければなりません。

staticメンバへのアクセス

Javaのstaticメンバは、そのクラスの「クラスオブジェクト」を形成します。そのようなクラスオブジェクトは値として渡したりできませんが、明示的にメンバにアクセスできます。

JavaからのKotlinコード呼び出し

将来的にはより多くのプラットフォームをターゲットとする予定ですが、今のところKotlinはJavaプラットフォーム用にだけコンパイルされます。これはコンパイラがJavaバイトコードを生成することを意味しており、したがってKotlinで書いたコードはJavaから呼ぶことが出来ます。KotlinにはJavaでは有効でないコンセプトがいくつかあります。このセクションで、これらのコンセプトがJavaのコンセプトとどうマップされるのか簡単に説明します。

名前空間レベルの関数

org.foo.bar名前空間(namespace)内で宣言されている関数やプロパティはすべて、org.foo.bar.namespaceと名付けられたJavaクラスとなります。
※namespaceは古い仕様です。現在はnamespaceは廃止され、packageが使われています。

チェック例外

先に述べたとおり、Kotlinはチェック例外を持ちません。よって通常、Kotlinの関数のJavaシグネチャは例外を投げる宣言がされません。例えば、IOExceptionを投げるKotlinの関数をJavaコードで呼び出す際に、それを捕捉するためのcatchブロックを書くとコンパイルエラーとなります。では、どうすればよいのでしょう。いくつかの選択肢が挙げられます。

  • 一つ目の選択肢(コメント欄で提案された*3)は、Javaでニセの例外を投げる関数を作ること
// Java
<E extends Throwable> void mayThrow(Class<E> eClass) throws E {
  // Do nothing
}

http://confluence.jetbrains.net/display/Kotlin/Java+interoperability より引用

そして、こう書く。

// Java
try {
  mayThrow(IOException.class);
  demo.namespace.foo();
}
catch (IOException e) { // No problem
  // ...
}

http://confluence.jetbrains.net/display/Kotlin/Java+interoperability より引用

  • 2つ目の選択肢はThrowableを捕捉し、instanceofチェックをすること。これはあまりエレガントではないが、動作する。
  • 3つ目の選択肢は、Javaでラッパ関数を書く。
  • 4つ目の選択肢は、Kotlinの関数シグネチャにthrowsアノテーションと共にthrowsリストを書く。
throws<IOException> fun foo() {
  throw IOException();
}

http://confluence.jetbrains.net/display/Kotlin/Java+interoperability より引用

実行時型情報

Kotlinのジェネリクスは実行時に保持され(つまりオブジェクト生成時に)、コンストラクタへのパラメータとして型情報を渡します。

// Java
new SomeClassDesclaredInKotlin<Integer>(TypeInfos.Int()) // 明示的にTypeInfoオブジェクトを渡している

ジェネリック関数も同様です。

NULL安全

JavaからKotlinの関数を呼び出すとき、非nullパラメータとしてnullを渡すことに関して誰も妨げません。非null値を期待するpublicな関数のための実行時チェックはKotlinが生成するからです。この方法はすぐにJavaコードでNullPointerExceptionをもたらします。

プロパティ

プロパティのゲッタはgetメソッドへ、セッタはsetメソッドへ変わります。

*1:邦訳:waitとnotifyよりコンカレンシーユーティリティを選ぶ

*2:邦訳:cloneを注意してオーバーライドする

*3:http://confluence.jetbrains.net/display/Kotlin/Java+interoperability のコメント欄のことです