Tengo dos código casi idéntica en Java y Kotlin
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)
}
El código Java pasa la prueba con una gran entrada, pero el código Kotlin causa una StackOverFlowError
menos que añadí tailrec
palabra clave antes de la helper
función en Kotlin.
Quiero saber por qué esta opción funciona en Java y también en Kolin con tailrec
pero no en Kotlin sin tailrec
?
PS: Yo sé lo que tailrec
hacer
Quiero saber por qué esta función trabaja en java y también en Kotlin con
tailrec
pero no en Kotlin sintailrec
?
La respuesta corta es porque su Kotlin método es más "pesado" que el JAVA uno. En cada llamada del que llama a otro método que "provoca" StackOverflowError
. Por lo tanto, ver una explicación más detallada a continuación.
equivalentes de código de bytes de Java para reverseString()
He comprobado el código de bytes para sus métodos en Kotlin y JAVA en consecuencia:
método Kotlin bytecode en JAVA
...
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);
}
}
...
método Java bytecode en 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);
}
}
...
Por lo tanto, hay 2 diferencias principales:
Intrinsics.checkParameterIsNotNull(s, "s")
se invoca para cada unohelper()
en el Kotlin versión.- Izquierdo y derecho índices en JAVA método GET incrementan, mientras que en Kotlin se crean nuevos índices para cada llamada recursiva.
Por lo tanto, la prueba de Let cómo Intrinsics.checkParameterIsNotNull(s, "s")
solo afecta al comportamiento.
Probar ambas implementaciones
He creado una prueba simple para ambos casos:
@Test
public void testJavaImplementation() {
char[] chars = new char[20000];
new Example().reverseString(chars);
}
Y
@Test
fun testKotlinImplementation() {
val chars = CharArray(20000)
Example().reverseString(chars)
}
Para JAVA la prueba tuvo éxito y sin problemas mientras que para Kotlin fracasó miserablemente debido a una StackOverflowError
. Sin embargo, después de añadir Intrinsics.checkParameterIsNotNull(s, "s")
a la JAVA método fracasó así:
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);
}
Conclusión
Su Kotlin método tiene un nivel de recursividad más pequeño, ya que invoca Intrinsics.checkParameterIsNotNull(s, "s")
a cada paso y por lo tanto es más pesado que su JAVA contraparte. Si no desea que este método de auto-generado, entonces puede desactivar cheques nulos durante la compilación como respondida aquí
Sin embargo, puesto que entiende qué beneficio tailrec
trae (convierte la llamada recursiva en un proceso iterativo), debe usar esa.