programacion - ¿Cómo funciona el borrado en Kotlin?
manual de programacion android pdf (2)
En Kotlin, el siguiente código compila:
class Foo {
fun bar(foo: List<String>): String {
return ""
}
fun bar(foo: List<Int>): Int {
return 2;
}
}
Este código, sin embargo, no:
class Foo {
fun bar(foo: List<String>): String {
return ""
}
fun bar(foo: List<Int>): String {
return "2";
}
}
Compilar esto causará el siguiente error:
Error:(8, 5) Kotlin: Platform declaration clash: The following declarations have the same JVM signature (foo(Ljava/util/List;)Ljava/lang/String;):
fun foo(layout: List<Int>): String
fun foo(layout: List<String>): String
En Java, ningún ejemplo compilará:
class Foo {
String bar(List<Integer> foo) {
return "";
}
Integer bar(List<String> foo) {
return 2;
}
}
class Foo {
String bar(List<Integer> foo) {
return "";
}
String bar(List<String> foo) {
return "2";
}
}
Como era de esperar, ambos fragmentos de código anteriores generan el familiar error de compilación:
Error:(13, 12) java: name clash: bar(java.util.List<java.lang.String>) and bar(java.util.List<java.lang.Integer>) have the same erasure
Lo que me sorprende es que el primer ejemplo de Kotlin funciona, y segundo, si funciona, ¿por qué falla el segundo ejemplo de Kotlin? ¿Kotlin considera el tipo de retorno de un método como parte de su firma? Además, ¿por qué las firmas de métodos en Kotlin respetan el tipo de parámetro completo, en contraste con Java?
En realidad, Kotlin sabe la diferencia entre los dos métodos en su ejemplo, pero jvm no lo hará. Por eso es un choque de "plataforma".
Puede hacer que su segundo ejemplo se compile utilizando la anotación @JvmName
:
class Foo {
@JvmName("barString") fun bar(foo: List<String>): String {
return ""
}
@JvmName("barInt") fun bar(foo: List<Int>): String {
return "2";
}
}
Esta anotación existe por esta misma razón. Puedes leer más en la documentación de interoperabilidad .
Si bien la respuesta de @Streloks es correcta, quería profundizar más en cuanto a por qué funciona.
La razón por la que la primera variante funciona es que no está prohibido dentro del código de Byte de Java. Si bien el compilador de Java se queja de ello, es decir, la especificación del lenguaje Java no lo permite , el código de Byte lo hace, como también se documentó en https://community.oracle.com/docs/DOC-983207 y en https://www.infoq.com/articles/Java-Bytecode-Bending-the-Rules . En el código de Byte, cada llamada de método refiere el tipo de retorno real del método, que no es así cuando se escribe el código.
Lamentablemente no pude encontrar la fuente real, por qué es así.
El documento sobre la resolución de nombres de Kotlins contiene algunos puntos interesantes, pero no vi su caso real allí.
Lo que realmente me ayudó a entenderlo fue la respuesta de @Yole al borrado de tipo Kotlin: ¿por qué las funciones se diferencian solo en el tipo genérico compilable mientras que las que solo difieren en el tipo de retorno no lo son? , más precisamente que el compilador de kotlin no tendrá en cuenta el tipo de variable al decidir qué método llamar.
Por lo tanto, fue una decisión de diseño deliberada que especificar el tipo en una variable no influirá en qué método es el que debe llamarse, sino más bien al revés, es decir, el método llamado (con o sin información genérica) influye en el tipo que se usará .
Aplicar la regla en las siguientes muestras tiene sentido:
fun bar(foo: List<String>) = "" (1)
fun bar(foo: List<Int>) = 2 (2)
val x = bar(listOf("")) --> uses (1), type of x becomes String
val y = bar(listOf(2)) --> uses (2), type of y becomes Int
O tener un método que suministre un tipo genérico pero ni siquiera usarlo:
fun bar(foo: List<*>) = "" (3)
fun <T> bar(foo: List<*>) = 2 (4)
val x = bar(listOf(null)) --> uses (3) as no generic type was specified when calling the method, type of x becomes String
val y = bar<String>(listOf(null)) --> uses (4) as the generic type was specified, type of y becomes Int
Y esa es también la razón por la que lo siguiente no funcionará:
fun bar(foo: List<*>) = ""
fun bar(foo: List<*>) = 2
Esto no es compilable, ya que conduce a una sobrecarga conflictiva, ya que el tipo de variable asignada en sí no se tiene en cuenta al tratar de identificar el método a llamar:
val x : String = bar(listOf(null)) // ambiguous, type of x is not relevant
Ahora, con respecto a ese conflicto de nombres: tan pronto como use el mismo nombre, el mismo tipo de retorno y los mismos parámetros (cuyos tipos genéricos se borran), obtendrá la misma firma de método en el código de byte. Es por eso que @JvmName
vuelve necesario. Con eso realmente se asegura de que no haya conflictos de nombres en el código de byte.