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

Kotlin Advent Calendar 2012 (全部俺)

JavaプログラマのためのKotlin入門

22日目:Javaとの相互運用

f:id:ngsw_taro:20071123143347j:plain

3連休初日キタ━(・∀・)━!!!

アドベントカレンダー初日にお話ししましたが、KotlinはJVM言語なのでコンパイラが吐くのはJavaバイトコードです。それらはJava言語から使用することができます。またその逆に、Java言語で書かれたクラスをKotlinから使用することもできます。今日はKotlinとJavaの相互運用についてお話しします。

KotlinからJavaコードを使う

Java→Kotlin

同じオブジェクト指向言語として、やはりJavaとKotlinには類似点が多くあります。例えばJavaの void はKotlinの Unit にそのまま対応しています。このような対応関係をまずは把握しましょう。

JavaコードKotlinではこうなる
返値型がvoidのメソッド 返値型がUnitの関数
Kotlinの予約語と同一の識別子 使用する際はバッククオートで括る(`is`など)
参照型(例えばInteger) NULL許容型(例えばInt?)
チェック例外 非チェック例外(捕捉の義務はない)

ジェネリクスの境界ワイルドカード

 List<? extends Hoge>

 List<? super Hoge>

型プロジェクション(使用箇所宣言)

List<out Hoge>

List<in Hoge>

raw型(原型)

List

スタープロジェクション

List<*> つまり List<out Any?>

java.lang.Object Any?

java.lang.Objectが提供するメソッド

Kotlinでは、すべてのクラスのスーパクラスは Any です。上記の表のようにJavaコードにおけるObject型はKotlinで扱う際にAny?型に変換されます。しかしクラス Any にはいかなるメンバも定義されていません。それでは Object の提供するメソッドはどのようにKotlin側で扱えばよいのでしょうか。

メソッド toString とequals、finalize、clone()は、プログラマ視点では特に違いはありません。Kotlinがどのような仕掛けでそれらのメソッドを呼び出しているか気になる方は、Kotlinのソースコードを読んでみてください。メソッド hashCode、wait、notify、getClassは、Javaコードで定義されたクラスのみが持ちます。wait、notifyがKotlinで廃止されていることはいいことです*1

JavaからKotlinコードを使う

名前空間レベルの関数

KotlinではJavaと異なり名前空間レベルに関数を置くことができます。実はコンパイルすると、namespaceという名前のクラスが自動生成され、そのクラスが持つ staticメソッドとなります。

例外

Kotlinにはチェック例外はありません。すべて非チェック例外です。これは単にjava.lang.RuntimeExceptionかそのサブクラスであるということではなく、文字通り非チェック例外なのです。

KotlinではJavaと同じように例外を投げることができますが、関数にスロー宣言はありません。例えば次のようなKotlinコードがあるとします。

fun throwIOException() {
    throw java.io.IOException()
}

fun main(args : Array<String>) {
    try {
        throwIOException()
    } catch(e : java.io.IOException) {
        e.printStackTrace()
    }
}

このコードはコンパイル可能です。実行するとスタックトレースが表示されるだけのプログラムです。

上記の関数 throwIOException をJavaコードから呼び出して、上記の main と同じようなコードを書いてみます。

// Java
public class Sample {
    public static void main(String[] args) {
        try {
            namespace.throwIOException();
        } catch(java.io.IOException e) {
            e.printStackTrace();
        }
    }
}

名前空間レベルの関数」で説明したように、関数 throwIOException はクラス namespace 内のstatic関数となるので、同じパッケージ内にいれば例のJavaコードのように呼び出すことができます。しかし、このコードはコンパイル失敗します。問題の箇所は、catch節です。try節からIOExceptionはスローされ得ないという解釈がされるからです。繰り返しになりますが、Kotlinの関数はスロー宣言ができないので、このようなことが起こるのです。

この問題に対してKotlinプロジェクトチームは次のような解決策を挙げています。

  • スロー宣言だけで、何も仕事をしないメソッドを定義し、それとセットで使う
  • Throwableで補足してinstanceofチェックをする。あまりエレガントじゃない
  • Javaでラッパメソッドを書く
  • Kotlin側の関数に、アノテーションthrowsを用いてスロー宣言を行う(現時点では実装されてなさそう...)

プロパティ

 プロパティには、setterとgetterを使ってアクセスします。

class Person() {
    var name : String = ""
}
// Java
final Person taro = new Person();
taro.setName("Taro");
System.out.println(taro.getName()); // Taro

まとめと次回予告

今日はKotlinとJavaとの相互運用についてお話ししました。両者の違いと、その差異を吸収する仕組みを知ることが相互運用の場面では重要になると思います。もっとも、KotlinコードをJavaから使用することはあまりないかも知れませんが。

明日はJavaユニットテストフレームワークとして名高いJUnitと、Kotlinの標準ライブラリを使ったユニットテストの書き方を紹介します。

日記

東京では、クリスマスイブに雪が降るらしいですね。

*1:Effective Java 第2版の「項目69 waitとnotifyよりコンカレンシーユーティリティを選ぶ」を参照してください。