Kotlin es una caja de sorpresas. Aunque de por sí ya existen muchas funcionalidades de Kotlin que no existen en Java, esto solo es rascar la superficie de lo que nos puede ofrecer.

En el artículo de hoy te muestro algunas cosas que no sabías y una serie de curiosidades que te pueden ayudar a sacar aún más partido al lenguaje.

Si estás empezando con Kotlin, te animo a que te apuntes a mi training gratuito para aprenderlo todo sobre el lenguaje.

1. Se pueden crear funciones de extension sobre tipos nulables

Es posible que ya conocieras las funciones de extensión. ¿Pero sabías que las puedes aplicar sobre tipos nullables? Esto significa que una variable que puede contener un nulo la puede llamar sin necesidad de hacer el chequeo de nulidad.

Esto ya se gestionará dentro.

fun CharSequence?.isNullOrEmpty(): Boolean {
    return this == null || this.length == 0
}

2. Existe el tipo Nothing que extiende a todos los tipos

Seguramente tengas más clara la idea de Any, un tipo del que extienden todos los tipos. Este es el equivalente de Object en Java.

Pero lo que quizá no tengas tan claro es el tipo Nothing. Es un tipo que extiende de todos los tipos. Por tanto, cualquier función puede devolver un Nothing independientemente del tipo que devuelva.

Si una función devuelve Nothing, significa que nunca va a devolver un valor.

3. Las excepciones y return devuelven Nothing

Esto es muy interesante, porque eso significa por ejemplo que cuando estamos asignando valores a las distintas ramas de un when, podemos tener casos en los que asignemos expresiones de tipo Nothing.

Por ejemplo:

        val result = when (x) {
            1 -> "1"
            2 -> "2"
            3 -> return
            else ->throw IllegalStateException()
        }

4. La función TODO() devuelve Nothing

Si te preguntabas por alguna aplicación en particular de esto, hecha un vistado a la función TODO():

fun TODO(): Nothing = throw NotImplementedError()

5. Hay veces que el smartcast no funciona

El smartcast es una función súper potente, pero podemos encontrarnos situaciones en las que el compilador no pueda asegurar que un valor vaya a seguir siendo el mismo en la siguiente línea. Por ejemplo aquí:

Si una propiedad es pública, cualquier otro hilo podría haber cambiado su valor de una línea a la siguiente.

6. Existe un casting seguro

En el ejemplo anterior, nos podemos sentir tentados a hacer lo siguiente:

if (x is Int) {
    val y: Int = x as Int
}

Esto nos podría dar el mismo problema. Por suerte, en Java tenemos un casting que nos permite devolver nulo en el caso de que el tipo no coincida:

val y: Int? = x as? Int

7. Comprobar la nulidad es solo un caso concreto de smartcast

En el caso que veíamos antes, realmente lo que ocurre para que dentro del if podamos usar y sin preocuparnos por la nulidad, es que el compilador hace un smartcast de Int? a Int

8. Los tipos no nulables extienden de los nulables

Esta es la razón por la que podemos guardar un valor de tipo Int en un Int?, pero no al revés:

val x: Int? = 20

9. Nuestras funciones pueden hacer smartcast

Cuando usas una función isNullOrEmpty() que veíamos al principio ocurre lo siguiente:

¿Cómo puede ser esto? La función le está dando información al compilador de lo que ocurre dentro de sí misma.

Esto se conoce como contracts (o contratos), y aunque para usarlo nosotros en nuestras propias funciones aún es una característica experimental, la función estándar ya lo usa en algunas partes:

public inline fun CharSequence?.isNullOrEmpty(): Boolean {
    contract {
        returns(false) implies (this@isNullOrEmpty != null)
    }

    return this == null || this.length == 0
}

10. No existen tipos básicos, ni por tanto conversiones implícitas

En Java, tú puedes hacer:

int x = 20;
long y = x;

En Kotlin esto no es posible porque no hay tipos básicos: todo es un objeto. Esto tiene muchas ventajas, pero tiene un pequeño coste: las conversiones han de ser explícitas. Aunque Kotlin nos lo pone muy fácil:

val x = 20
val y = x.toLong()

11. Las operaciones a nivel de bit siguen existiendo

A veces nos cuesta encontrarlas viniendo de Java, porque no son operadores, sino funciones. Existe todas las habituales, pero por nombrar algunas:

x and y
x or y
x xand y
x xor y

12. Las funciones infijas no son magia

Quizá te has dado cuenta de que las funciones del punto anterior eran un poco raras. Podemos crear las nuestras propias usando la palabra reservada infix. Por ejemplo:

public infix fun and(other: Int): Int

13. Existen enteros sin signo

Aunque en el momento de escribir estas líneas aún es una característica experimental, existen los siguientes tipos:

UByte
UShort
UInt
ULong

14. Esto es posible gracias a las clases inline

Puede que sepas que existen las funciones inline, que en tiempo de compilación sustituyen el cuerpo de la función por su código.

El equivalente en clases también existe de forma experimental en Kotlin 1.3

Esto nos permite crear tipos más especializados sobre otro tipo genérico, pero sin la carga extra de tener que crear una clase a nivel de bytecode.

inline class Password(val value: String)

15. En Kotlin puedes escribir Strings multilínea

En Java la única forma de conseguir esto era escribiendo cada línea con el operador “+” y haciendo explícitos los caracteres de salto de línea.

En Kotlin es tan fácil como rodearlo con triple comilla:

val text= """
    This is a text
    of several lines
    in just one variable
    """

16. Las funciones sobrescritas en Kotlin son abiertas por defecto

Esto quiere decir que si tenemos una clase hija que sobrescribe una función de la clase padre, la clase “nieta” podría sobrescribirla a su vez.

Pero esto se puede cerrar usando la palabra final:

17. Puedes comprobar si una variable lateinit está inicializada

Desde Kotlin 1.2, podemos hacer lo siguiente:

lateinit var lateVar: String
...
if (::lateVar.isInitialized){
    // Do only if initialized
}

De esa forma, en vez de producirse una excepción si la usamos antes de inicializarla, podemos tomar una solución controlada, o incluso usarlo para solo inicializarla una vez.

18. Puede haber conflictos al implementar interfaces con código

Si dos interfaces tienen la misma función, y al menos una de ellas está implementada, necesitamos decirle a la clase que las extienda qué debe hacer. Por tanto, nos va a obligar a implementarla:

Lo que podemos hacer es especificar cómo queremos que use las funciones de las clases padre de la siguiente formma:

class C : A, B {
    override fun foo() {
        super<A>.foo()
        super<B>.foo()
    }
}

19. También puedes crear propiedades de extensión

Si llevas un tiempo con Kotlin, seguro que ya conoces las funciones de extension, lo que quizá no conocías son las propiedades de extensión.

El concepto es el mismo, podemos definir Properties para cualquier clase aunque no tengamos acceso a su código. El valor tiene que poder componerse con las propiedades y funciones públicas de la clase, ya que la propiedad de extensión no puede almacenar estado:

val <T> List<T>.lastIndex: Int
    get() = size - 1

20. Y puedes añadir extensiones a los Companion Objects

Cada clase puede tener un companion object que comparten todas las instancias de esa clase. A este objeto también le podemos incluir extensiones de la siguiente forma:

class Person {
    companion object {}
}

fun Person.Companion.printPerson() {
    println("Person")
}

fun test(){
    Person.printPerson()
}

El objeto recibe el nombre Companion. Como al companion object se puede acceder sin escribirlo explícitamente, la pinta será la de un método estático de los de Java.

21. Las clases internas, por defecto, no tienen acceso al estado de las externas

A diferencia de Java, donde por defecto sí que es posible, en Kotlin se restringe esta opción a no ser que se indique explícitamente.

Por ejemplo, si la clase Address necesita acceder al estado de Person, no podría:

Para que esto funcione, habría que añadir inner como modificador de la clase:

22. Puedes dar type aliases a las clases internas

Los type aliases permiten darle un nombre más representativo a un tipo complejo. Esto es muy útil en programación funcional cuando los tipos se vuelven muy complejos, con funciones que reciben funciones, devuelven funciones, etc.

Pero también nos puede servir para dar nombres a las clases internas. Por ejemplo:

class Person {
    class Address
}

typealias PersonAddress = Person.Address

23. El delegado map

Seguro que si llevas un tiempo con Kotlin, ya has usado alguna vez delegados como lazy u observable. Los delegados permiten ceder el control del getter y el setter de una property a un objeto externo.

Pero hay un delegado menos conocido que te permite extraer valores de una colección Map:

class User(val map: Map<String, Any?>) {
    val name: String by map
    val age: Int     by map
}

Lo que hará es usar el nombre de la property como clave en el Map para buscar su valor.

24. Puedes usar delegados locales

Desde Kotlin 1.1, además de usar los delegados en las properties de una clase, puedes definir properties delegadas locales. A todos los efectos son variables que permiten usar los delegados. Por ejemplo:

fun foo(){
    val p: Person by lazy { Person() }
    ...
}

La variable p no se instanciará hasta que no se use por primera vez.

25. Puedes crear objetos anónimos locales

Imagina que tienes un código un poco complejo que quedaría mejor representado por una estructura de datos local. En cualquier lugar te puedes crear un objeto anónimo:

val localObject = object {
    val x = 20
    val y = "Hello"
}

Luego tan solo tienes que usarlo como lo harías con cualquier otro objeto:

println(localObject.x)
println(localObject.y)

Author: Antonio Leiva

Soy un apasionado de Kotlin. Hace ya más de dos años que estudio el lenguaje y su aplicación a Android para ayudarte a ti a aprenderlo de la forma más sencilla posible.