Kotlin fue diseñado inicialmente como una alternativa segura y concisa a Java. Una de sus grandes fortalezas es el desarrollado mecanismo de inferencia de tipos que permite escribir código menos verboso sin perder la tipificación. La inferencia de tipos se inspiró en lenguajes funcionales (como Scala y Haskell), así como en tendencias modernas de diseño de lenguajes estáticamente tipados.
En Java y otros lenguajes estáticos, se requiere indicar explícitamente los tipos, lo que lleva a una redundancia del código. Sin embargo, la falta de tipos explícitos puede dificultar la comprensión del código y llevar a errores no evidentes si la inferencia de tipos no funciona correctamente.
En Kotlin, el compilador puede a menudo determinar por sí mismo el tipo de una variable o expresión según el contexto. Esto funciona para variables, valores de retorno de funciones y dentro de expresiones con lambdas. Sin embargo, hay situaciones en las que el compilador requiere que se indique explícitamente el tipo, por ejemplo, al declarar una función sin valor de retorno dentro de una clase ('fun doSomething()') o cuando las expresiones son ambiguas.
Ejemplo de código:
val a = 42 // Int val s = "hello" // String fun sum(x: Int, y: Int) = x + y // el tipo de retorno Int se infiere automáticamente val list = listOf(1, 2, 3) // List<Int> // Se requiere indicación explícita del tipo si el valor no puede ser inferido val emptyList: List<String> = emptyList() // de lo contrario será List<Nothing>
Características clave:
¿Por qué no se puede omitir siempre el tipo después de los dos puntos, por ejemplo, para propiedades de clase?
Para las propiedades inicializadas no en el lugar de la declaración (por ejemplo, a través de un getter o en un bloque init), el compilador no puede inferir el tipo automáticamente porque no ve el inicializador.
class User { val fullName: String // Debe indicar el tipo, de lo contrario error get() = "name" }
¿Cuál será el tipo de la variable si se usa emptyList() sin un tipo explícito?
Se inferirá el tipo List<Nothing>, lo que hace que el resultado sea prácticamente inútil.
val list = emptyList() // List<Nothing>
¿Cuándo no funciona la inferencia de tipos con parámetros de funciones?
En la firma de una función, siempre se requiere indicar explícitamente los tipos de los parámetros; de lo contrario, el compilador generará un error.
// Error: // fun foo(x) = x * 2 // Correcto: fun foo(x: Int) = x * 2
Un desarrollador utiliza emptyList() para devolver un valor de una función API sin indicar el tipo explícitamente. Como resultado, se obtiene el tipo List<Nothing>, lo que causa problemas al trabajar con esta API.
Ventajas:
El desarrollador siempre indica explícitamente el tipo al trabajar con colecciones vacías y donde mejora la legibilidad, y en otros casos confía en la inferencia de tipos del compilador.
Ventajas: