Javaとkotlinにほぼ同じ2つのコードがあります
Java:
public void reverseString(char[] s) {
helper(s, 0, s.length - 1);
}
public void helper(char[] s, int left, int right) {
if (left >= right) return;
char tmp = s[left];
s[left++] = s[right];
s[right--] = tmp;
helper(s, left, right);
}
Kotlin:
fun reverseString(s: CharArray): Unit {
helper(0, s.lastIndex, s)
}
fun helper(i: Int, j: Int, s: CharArray) {
if (i >= j) {
return
}
val t = s[j]
s[j] = s[i]
s[i] = t
helper(i + 1, j - 1, s)
}
Javaコードは巨大な入力でテストに合格しますが、StackOverFlowError
キーワードをtailrec
関数の前に追加しない限り、kotlinコードはhelper
を引き起こしますコトリンで。
この関数がJavaおよびtailrec
を使用するkolinで機能するが、tailrec
を使用しないkotlinでは機能しない理由を知りたいですか?
P.S:tailrec
が何をするか知っています
この関数がJavaとkotlinで
tailrec
を使用してkotlinでtailrec
を使用せずに機能する理由を知りたいですか?
短い答えは、KotlinメソッドがJavaの1つよりも「重い」ためです。呼び出しのたびに、それはStackOverflowError
を「引き起こす」別のメソッドを呼び出します。したがって、以下の詳細な説明を参照してください。
Java reverseString()
と同等のバイトコード
KotlinおよびJavaで対応するように、メソッドのバイトコードを確認しました。
JavaでのKotlinメソッドのバイトコード
...
public final void reverseString(@NotNull char[] s) {
Intrinsics.checkParameterIsNotNull(s, "s");
this.helper(0, ArraysKt.getLastIndex(s), s);
}
public final void helper(int i, int j, @NotNull char[] s) {
Intrinsics.checkParameterIsNotNull(s, "s");
if (i < j) {
char t = s[j];
s[j] = s[i];
s[i] = t;
this.helper(i + 1, j - 1, s);
}
}
...
JavaのJavaメソッドバイトコード
...
public void reverseString(char[] s) {
this.helper(s, 0, s.length - 1);
}
public void helper(char[] s, int left, int right) {
if (left < right) {
char temp = s[left];
s[left++] = s[right];
s[right--] = temp;
this.helper(left, right, s);
}
}
...
したがって、2つの主な違いがあります。
Intrinsics.checkParameterIsNotNull(s, "s")
は、Kotlinバージョンのhelper()
ごとに呼び出されます。それでは、Intrinsics.checkParameterIsNotNull(s, "s")
だけがどのように動作に影響するかをテストしてみましょう。
両方の実装をテスト
両方のケースで簡単なテストを作成しました。
@Test
public void testJavaImplementation() {
char[] chars = new char[20000];
new Example().reverseString(chars);
}
そして
@Test
fun testKotlinImplementation() {
val chars = CharArray(20000)
Example().reverseString(chars)
}
Javaの場合、テストは問題なく成功しましたが、Kotlinの場合、StackOverflowError
が原因で惨めに失敗しました。ただし、Intrinsics.checkParameterIsNotNull(s, "s")
を追加した後Javaメソッドにも同様に失敗しました:
public void helper(char[] s, int left, int right) {
Intrinsics.checkParameterIsNotNull(s, "s"); // add the same call here
if (left >= right) return;
char tmp = s[left];
s[left] = s[right];
s[right] = tmp;
helper(s, left + 1, right - 1);
}
結論
Kotlinメソッドは、すべてのステップでIntrinsics.checkParameterIsNotNull(s, "s")
を呼び出すため、再帰の深さが小さく、Javaよりも重い)です。この自動生成されたメソッドが必要ない場合は、回答時にコンパイル中にnullチェックを無効にすることができます here
ただし、tailrec
がもたらす利点(再帰呼び出しを反復呼び出しに変換する)を理解しているので、それを使用する必要があります。
Kotlinは、スタックが少しだけ空腹です(Intオブジェクトパラメータi.o. intパラメータ)。ここに当てはまるtailrecソリューションの他に、xor-ingによってローカル変数temp
を削除できます:
fun helper(i: Int, j: Int, s: CharArray) {
if (i >= j) {
return
} // i: a j: b
s[j] ^= s[i] // j: a^b
s[i] ^= s[j] // i: a^a^b == b
s[j] ^= s[i] // j: a^b^b == a
helper(i + 1, j - 1, s)
}
これがローカル変数を削除するために機能するかどうかは完全にはわかりません。
また、jを削除すると、次のようになります。
fun reverseString(s: CharArray): Unit {
helper(0, s)
}
fun helper(i: Int, s: CharArray) {
if (i >= s.lastIndex - i) {
return
}
val t = s[s.lastIndex - i]
s[s.lastIndex - i] = s[i]
s[i] = t
helper(i + 1, s)
}