1. 概要

この短いチュートリアルでは、オブジェクト式を使用してKotlinで匿名の内部クラスを作成する方法を説明します。

まず、匿名内部クラス用のKotlinAPIについて説明します。 次に、バイトコードレベルで物事がどのように表されるかを詳しく調べます。

2. 匿名の内部クラス

Javaでは、 new ClassName(){…}」構文を使用して匿名内部クラスを作成できます。 たとえば、ここでは、NIOチャネルインターフェイスの匿名内部クラスを作成しています。

Channel channel = new Channel() {
            
    @Override
    public boolean isOpen() {
        return false;
    }

    @Override
    public void close() throws IOException {
        // omitted
    }
};

Kotlinで匿名の内部クラスを作成するには、オブジェクト式を使用する必要があります。 たとえば、Kotlinで同じことを実現する方法は次のとおりです。

val channel = object : Channel {
    override fun isOpen() = false

    override fun close() {
        // omitted
    }
}

新しいキーワードの代わりに、「object:」を使用してオブジェクト式の構文を表します。 スーパータイプにコンストラクターがある場合は、必要なパラメーターをそのコンストラクターに渡す必要があります:

val maxEntries = 10000
val lruCache = object : LinkedHashMap<String, Int>(50, 0.75f) {
    override fun removeEldestEntry(eldest: MutableMap.MutableEntry<String, Int>?): Boolean {
        return size > maxEntries
    }
}

ここでは、 LinkedHashMap を使用して、カスタムの初期容量と負荷率で匿名LRUキャッシュを作成しています。 コンストラクターにパラメーターを渡したくない場合でも、空の括弧を入れる必要があります

val map = object : LinkedHashMap<String, Int>() {
    // omitted
}

さらに、1つのクラスに加えて複数のインターフェース、または単に複数のインターフェースから拡張できます。

val serializableChannel = object : Channel, Serializable {
    // omitted
}

非常に興味深いことに、別のクラスやインターフェイスを拡張することなく匿名の内部クラスを作成できます

val obj = object {
    val question = "answer"
    val answer = 42
}
println("The ${obj.question} is ${obj.answer}")

匿名の内部クラスを作成する方法がわかったので、バイトコードがどのようになるかを見てみましょう。

3. バイトコードレベル

生成されたバイトコードピークするには、最初にkotlincを使用してKotlinコードをコンパイルする必要があります。 Kotlinファイル名がAnonymous.ktの場合、次のコマンドはKotlinコードをクラスファイルにコンパイルします。

$ kotlinc Anonymous.kt

次に、「javap-p-c-v」を介してバイトコードを確認できます。 たとえば、チャネル変数がバイトコードレベルでどのように表されるかを次に示します。

$ javap -c -p -v AnonymousKt
0: new           #11            // class AnonymousKt$main$channel$1
3: dup
4: invokespecial #14           // Method AnonymousKt$main$channel$1."<init>":()V
// omitted
InnerClasses:
  public static final #11;    // class AnonymousKt$main$channel$1

上に示したように、 Kotlinコンパイラは、最初に「新しいチャネル{…}」式の静的内部クラスを生成します。 次に、コンストラクター( メソッド)それからインスタンスを作成します。

コンストラクターにいくつかの引数を渡すと、バイトコードは次のようになります。

16: bipush        10   // capacity
18: ldc           #17  // float 0.75f (load factor)
20: invokespecial #20  // Method AnonymousKt$main$lruCache$1."<init>":(IIF)V

生成された静的内部クラスのコンストラクターは、2つの整数と1つのfloatを入力として受け取ります— IIF部分。 これらの整数の1つは、実際には maxEntries 変数の値を取得するのに役立ち、他の2つはコンストラクターパラメーターです。

val maxEntries = 10
val lruCache = object : LinkedHashMap<String, Int>(10, 0.75f) {
    // omitted
}

このようにして、匿名の内部クラスはそれを囲む変数にアクセスできます。 これは、closureが匿名の内部クラスに対してどのように機能するかです。

重要なポイントは、Kotlinコンパイラが匿名の内部クラスをバイトコードの静的内部クラスに変換することです。

4. 結論

このクイックチュートリアルでは、オブジェクト式を使用して匿名の内部クラスを宣言する方法を説明しました。 また、オブジェクト式の内部表現についても理解しました。

いつものように、すべての例はGitHubから入手できます。