kotlinはJavaからどう見えるか?

  • 140
    Like
  • 0
    Comment
More than 1 year has passed since last update.

kotlinはjavaとの互換性を念頭において設計されています。では、kotlinとJavaが共存している時にkotlin特有の実装と思われる機能は実際にJavaからどのように見えるのでしょうか?

プロパティ

kotlinでvalvarでプロパティの宣言をしてみます。

KotlinSample.kt
class KotlinClassA{
    val a:Int = 1
    var b:Int = 2
}

これがJavaでは

MainActivity_java_-_kotlin-java-sample_-____Documents_github_kotlin-java-sample_.png

val a:Intで宣言したものはgetA()のgetterのみ
var b:Intで宣言したものgetB()setB(int i)とgetterとsetterの両方が生成されているようです。

get/setを使わずにプロパティアクセスしたい

kotlinのプロパティに@JvmFieldアノテーションを指定することでJavaからはget/setメソッドではなく直接プロパティとしてアクセスすることができます。

kotlin
class KotlinClassA{
    @JvmField val a:Int = 1
    @JvmField var b:Int = 2
}

これでJavaからはプロパティとしてアクセスすることができます。

Java
        KotlinClassA classA = new KotlinClassA();
        int x = classA.a;
        int y = classA.b;
        classA.b = 4;

valで宣言されたプロパティはjavaとしてはfinal指定であると解釈されているため以下の記述はビルドエラーになります。

Java
        classA.a = 5; // エラー: final変数aに値を代入することはできません

トップレベルの関数

kotlinではソースファイル上に直接関数を宣言することができます。一方Javaではclass内のstaticあるいはinstanceメソッドとしてしか手続きを実装することができません。

こんな感じにKotlinSample.ktというファイルに関数の実装をしてみると

KotlinSample.kt

fun func():Int = 3

これがJavaでは

MainActivity.java

    KotlinSampleKt.func();

ファイル名のKotlinSample.ktに対応するクラスKotlinSampleKtクラスが生成され、そのstaticメソッドとして実行することができます。

ファイルに対応するクラス名を変えたい

このKotlinSample.ktから由来するクラス名を変更したいときは、以下のようにファイルの先頭に記述することによって任意のクラス名に変更することができます。

※ ドキュメントには記載箇所の指定はありませんが、現状package指定よりも前の行に記述する必要があるようです。
※ また、以下の実装はビルドが通りますが、何故かAndroidStudio上ではエラーとして表示されます。

KotlinSample.kt

@file:JvmName("KotlinSampleB")
package com.github.yamamotoj.kotlin_java_sample

fun func():Int = 3

Javaでは

MainActivity.java

    KotlinSampleB.func();

のように指定したクラス名で実行することができます。

kotlinで宣言したinterface

上記のようにkotlinのプロパティはget/setメソッドとしてJavaからアクセスできます。これはinterfaceの実装でも同様です。

KotlinSample.kt
interface KotlinInterface{
    val a:Int
    var b:Int
}

このinterfaceをJavaで実装すると?

Java
    class KotlinInterfaceImpl implements KotlinInterface{

        @Override
        public int getA() {
            return 0;
        }

        @Override
        public int getB() {
            return 0;
        }

        @Override
        public void setB(int i) {

        }
    }

interfaceにデフォルト実装がある場合は?

kotlinではinterfaceにデフォルトの実装を持たせることができます。

kotlin
interface KotlinInterface2{
    val a:Int get() = 1
    fun getValue():Int {
        return a
    }
}

このinterfaceをJavaで実装すると?

Java
    class KotlinInterface2Impl implements KotlinInterface2{

        @Override
        public int getA() {
            return 0;
        }

        @Override
        public int getValue() {
            return 0;
        }
    }

普通にデフォルト実装は無視されるようですね ^^;

Companionオブジェクト

kotlinでクラスのstaticメソッドを実装するには以下のようにcompanion objectを介して実装します。

Kotlin
class KotlinClassB {
    companion object{
        fun funcB():Int{
            return 2
        }
    }
}

これをJavaからアクセスすると

Java
        KotlinClassB.Companion.funcB();

のようにCompanionオブジェクトを介してstaticにアクセスが可能です。

Companionオブジェクトを介さずにstaticメソッドにアクセスする

Javaからstaticにアクセスしたいメソッドに対しては@JmvStaticアノテーションを付けることで

Kotlin
class KotlinClassB {
    companion object{
        @JvmStatic fun funcB():Int{
            return 2
        }
    }
}

JavaからはCompanionオブジェクトを介さず直接メソッドにアクセスできます。

Java
        KotlinClassB.funcB();

シングルトンオブジェクト

kotlinではclass指定の代わりにobject指定をすることによりシングルトンオブジェクトの宣言をすることができます。

Kotlin
object KotlinObject{
    val a:Int = 1
}

このオブジェクトはkotlinのソースからは

Kotlin
    KotlinObject.a

とアクセスできますがJavaからは

Java
        KotlinObject.INSTANCE.getA();

のようにINSTANCEというオブジェクトを介してアクセスができます。

また、companion objectの場合と同様に直接アクセスしたいメソッドに@JvmStaticアノテーションを付けることで

Kotlin
object KotlinObject {
    @JvmStatic val a: Int = 1
}

Javaからは

Java
    KotlinObject.getA();

と直接アクセスすることができます。

Sealed class

sealed class

Kotlin
sealed class SealedClass {
    class A : SealedClass()
    class B : SealedClass()
}

Javaでは普通のインナークラスとして解釈されるようです。

Java
    SealedClass sealed = new SealedClass.A();

Extensions

kotlinではクラスのメソッド拡張の機能があり、継承を行わずとも既存のクラスに新たにメソッドを追加することができます。

Kotlin

class KotlinClassA {
}

class KotlinClassB {
    fun KotlinClassA.extendClassA(): Int = 3
}

このようにKotlinClassBからKotlinClassAのメソッドを拡張した場合、JavaではKotlinClassAを引数に持つKotlinClassBのメソッドとしてアクセスできます。

Java
        KotlinClassA classA = new KotlinClassA();
        KotlinClassB classB = new KotlinClassB();
        classB.extendClassA(classA);

トップレベルの関数としてメソッドを拡張した場合は、

KotlinSample.kt
class KotlinClassA {
}

fun KotlinClassA.extend(): Int = 3

前出のようにファイル名に由来するクラスのstaticメソッドとして拡張関数にアクセスできます。

Java
        KotlinClassA classA = new KotlinClassA();
        KotlinSampleKt.extend(classA);

Nullable

kotlinの便利な機能として?を使用したNullable指定があります。

Kotlin
class KotlinOptional {
    var nullableString:String? = null
    var nonNullString:String = "not null!"
}

これらはJavaからは取得する場合は代わりはありません

Java
        KotlinOptional optional = new KotlinOptional();
        String nonnull = optional.getNonNullString();
        String nullable = optional.getNullableString();

ただ、Nullableではない変数にnullを代入しようとするとその時点でNullPointerExceptionがthrowされます。

上記のようにStringだとJavaから見て?のある無しで違いが見られませんでしたがIntの場合は

Kotlin
class KotlinOptional2 {
    var nullableInt:Int? = null
    var nonNullInt:Int = 1
}

MainActivity_java_-_kotlin-java-sample_-____Documents_github_kotlin-java-sample_.png

nonNullの場合はint型でnullableの場合はInteger型なんですね。へぇー。

まとめ

kotlinでの実装もJavaから問題なくアクセスできるようで、安心して導入できますね。