Kotlinで文字列補間はどのように機能しますか?
1. 概要
Kotlinの文字列補間を使用すると、定数文字列と変数を簡単に連結して、別の文字列をエレガントに作成できます。
この記事では、生成されたバイトコードを見て、この補間が内部でどのように機能するかを確認します。 また、現在の実装で可能な将来の最適化についても見ていきます。
2. 文字列補間
簡単でおなじみの例から始めましょう。
class Person(val firstName: String, val lastName: String, val age: Int) {
override fun toString(): String {
return "$firstName $lastName is $age years old"
}
}
上に示したように、文字列補間を使用して toString()メソッドを実装しています。 Kotlinコンパイラがこの機能をどのように実装するかを確認するには、最初にkotlincを介してこのクラスをコンパイルする必要があります。
>> kotlinc interpolation.kt
次に、 javap ツールを使用して、生成されたバイトコードを確認できます。
>> javap -c -p com.baeldung.interpolation.Person
// truncated
public java.lang.String toString();
Code:
0: new #9 // class StringBuilder
3: dup
4: invokespecial #13 // Method StringBuilder."<init>":()V
7: aload_0
8: getfield #17 // Field firstName:LString;
11: invokevirtual #21 // Method StringBuilder.append:(LString;)LStringBuilder;
14: bipush 32
16: invokevirtual #24 // Method StringBuilder.append:(C)LStringBuilder;
19: aload_0
20: getfield #27 // Field lastName:LString;
23: invokevirtual #21 // Method StringBuilder.append:(LString;)LStringBuilder;
26: ldc #29 // String is
28: invokevirtual #21 // Method StringBuilder.append:(LString;)LStringBuilder;
31: aload_0
32: getfield #33 // Field age:I
35: invokevirtual #36 // Method StringBuilder.append:(I)LStringBuilder;
38: ldc #38 // String years old
40: invokevirtual #21 // Method StringBuilder.append:(LString;)LStringBuilder;
43: invokevirtual #40 // Method StringBuilder.toString:()LString;
46: areturn
このバイトコード命令のセットは、StringBuilderインスタンスを作成し、append()メソッドを使用して文字列の各部分を追加します。 基本的に、このバイトコードは次のJavaコードと同等です。
new StringBuilder()
.append(firstName)
.append(' ') // ascii code 32 or space
.append(lastName)
.append(" is ")
.append(age)
.append(" years old")
.toString()
したがって、文字列補間機能は、内部でStringBuilderクラスを使用しています。
2.1. 長所と短所
明るい面として、 StringBuilder の実装は非常にシンプルで、多くのJavaおよびKotlin開発者にとって理解しやすいものです。
ただし、テンプレート変数の数が増えると、バイトコードは長くなります。 処理および検証するバイトコードが増えるため、これはJVMの起動時間に影響します。
さらに、連結戦略はコンパイル時に固定されます。 したがって、新しいバージョンのコンパイラがより効率的なアプローチを使用している場合は、古いコードを再コンパイルして、その効率的な実装を利用する必要があります。
Java9とKotlin1.4.20がこれらの問題をどのように修正するかを見てみましょう。
3. ダイナミックを呼び出す
Invoke Dynamic(Indyとも呼ばれます)は、動的型付けされた言語のJVMサポートを強化することを目的とした JSR292の一部でした。 Java 9以降、 JEP 280 の一部として、Javaの文字列連結はinvokedynamicを使用しています。
この背後にある主な動機は、より動的な実装を行うことです。 つまり、バイトコードを変更せずに連結戦略を変更することが可能です。 このようにして、クライアントは、再コンパイルしなくても、新しい最適化された戦略の恩恵を受けることができます。 生成されたバイトコードも小さくなり、起動時間の短縮に貢献します。
Kotlin 1.4.20 以降、Kotlinコンパイラは文字列の連結にindyを利用できます。 そのためには、2つのことを行う必要があります。
- indyとの文字列連結はJava9以降でのみ使用可能であるため、JVMターゲットとして9以降を使用してください。
- -Xstring-concat コンパイラフラグを使用して、invokedynamic文字列連結を有効にします
したがって、これらのフラグを使用して同じコードを再コンパイルすると、次のようになります。
>> kotlinc -jvm-target 9 -Xstring-concat=indy-with-constants interpolation.kt
その場合、バイトコードは次のようになります。
>> javap -c -p com.baeldung.interpolation.Person
public java.lang.String toString();
Code:
0: aload_0
1: getfield #11 // Field firstName:LString;
4: aload_0
5: getfield #14 // Field lastName:LString;
8: aload_0
9: getfield #18 // Field age:I
12: invokedynamic #30, 0 // InvokeDynamic #0:makeConcatWithConstants:(LString;LString;I)LString;
17: areturn
上に示したように、バイトコードは、テンプレート変数の数に関係なく同じになります —はるかに単純で、信頼性が高く、冗長性が低くなります。
これは、Java9以降および9以上のJVMターゲットを備えたKotlin1.4.20以降でのみ機能することに注意してください。 また、Kotlin1.5ではindyをデフォルトの実装にする計画があります。
4. 結論
この簡単な記事では、Kotlinでの文字列補間の2つの異なる実装を見ました。1つは StringBuilder を使用し、もう1つはinvokedynamicを使用します。
いつものように、すべての例はGitHubでから入手できます。