Casting En Kotlin

En este tutorial te mostraremos cómo funciona el casting en Kotlin y varias de sus características auxiliares como Smart cast y operadores de casting seguro e inseguro.

Casting Inteligente En Kotlin

El compilador de Kotlin tiene la capacidad de realizar en una sola operación la verificación de tipo y casting. Permitiéndote usar inmediatamente el resultado del casting en el contexto aplicado.

A esta capacidad se le conoce como Smart cast o casting inteligente. Cuando usas el operador is, el compilador castea explícitamente el valor y hace consciente a las siguientes sentencias del resultado.

Por ejemplo:

Considera la declaración del tipo Movable, el cual permite responder a peticiones a los objetos que se muevan. Esta interfaz contendrá una operación para calcular la rapidez del objeto.

interface Movable {
    fun calculateSpeed(): Double
}

Ahora crearemos tres clases para representar bicicletas, autos y personas. Cada uno tendrá una propiedad base que permitirá deducir la rapidez. En las bicicletas influirá el tamaño de las llantas, para los autos los caballos de fuerza del motor y en las personas su peso corporal:

class Bike(val tireSize: Int) : Movable {
    override fun calculateSpeed() = tireSize * 5.0
}

class Car(val horsepower: Int) : Movable {
    override fun calculateSpeed() = horsepower * 0.62
}

class Person(val weigth: Double) : Movable {
    override fun calculateSpeed() = weigth * 0.72
}

Comprobemos el uso del smart cast creando una función que verifique los datos de un tipo Movable e imprima la rapidez que alcanza y su propiedad base:

fun main() {
    verify(Bike(22))
    verify(Car(300))
    verify(Person(60.0))
}

fun verify(movable: Movable) {
    if (movable is Bike) {
        print("Bicicleta corre a: ${movable.calculateSpeed()}km/h Tamaño de llanta: ${movable.tireSize}mm")
        return
    }
    if (movable is Car) {
        print("Auto corre a: ${movable.calculateSpeed()}km/h Caballos de fuerza: ${movable.horsepower}hp")
        return
    }
    if (movable is Person) {
        print("Persona corre a: ${movable.calculateSpeed()}km/h Peso: ${movable.weigth}kg")
        return
    }
}

Salida:

Bicicleta corre a: 110.0km/h Tamaño de llanta: 22mm
Auto corre a: 186.0km/h Caballos de fuerza: 300hp
Persona corre a: 43.199999999999996km/h Peso: 60.0kg

Como ves, el parámetro movable en ningún momento se castea, ya que el uso del operador is en cada expresión if lo hace automáticamente. Por esta razón podemos acceder a las variables particulares de cada clase en el bloque de código.

Smart Cast En Expresión When

El compilador extiende esta capacidad a la expresión when cuando realizas verificaciones de tipo en sus ramas. Por ejemplo, si refactorizamos la función anterior verás:

fun verify(movable: Movable){
    when(movable){
        is Bike -> println("Bicicleta corre a: ${movable.calculateSpeed()}km/h" +
                " Tamaño de llanta: ${movable.tireSize}mm")
        is Car -> println("Auto corre a: ${movable.calculateSpeed()}km/h " +
                "Caballos de fuerza: ${movable.horsepower}hp")
        is Person->println("Persona corre a: ${movable.calculateSpeed()}km/h " +
                "Peso: ${movable.weigth}kg")
        else -> throw IllegalArgumentException("Tipo desconocido")
    }
}

El uso de is en cada caso del when realiza el casting explícitamente para que el bloque de la rama use directamente a movable como el tipo verificado.

Reglas Para Smart Cast

Cuando el compilador encuentra una verificación de tipo e intenta realizar el casting, se asegura de que el valor no cambiará en el momento de usarse. Por lo cual, este proceso funciona solo en:

  • Variables locales de solo lectura: Siempre, excepto las propiedades locales delegadas
  • Propiedades de solo lectura: Solo si la propiedad es private, protected o si la verificación de tipo se realiza en el mismo módulo. No se aplica si la propiedad está marcada como open o si tiene getter personalizado.
  • Variables mutables locales: Solo si la variable no ha sido modificada entre la verificación y el uso, si no ha sido modificada por una función lambda y si no es una propiedad local delegada
  • Propiedades mutables: Nunca

Operador De Casting Inseguro

Para realizar un casting se usa el operador de infijo as. Con el expresas el enunciado «tomar operador izquierdo como tipo derecho».

Por ejemplo:

fun main() {
    showBike(Bike(30))
}

fun showBike(movable: Movable){
    val bike = movable as Bike
    print(bike.calculateSpeed())
}

Salida:

150.0

¿Que pasaría si usaramos la versión anulable de Movable?

fun main() {
    showBike(null)
}

fun showBike(movable: Movable?){
    val bike = movable as Bike
    print(bike.calculateSpeed())
}

Salida:

Exception in thread "main" java.lang.NullPointerException: null cannot be cast to non-null type Bike

Aunque el compilador no marca un aviso al usar as con un tipo anulable, en la ejecución se lanzará un NPE al pasar null en showBike().

La forma de evitar esta excepción, es declarar a bike como Bike?:

fun main() {
    showBike(null)
}

fun showBike(movable: Movable?){
    val bike = movable as Bike?
    print(bike?.calculateSpeed())
}

Salida:

null

Operador De Casting Seguro

El casting entre tipos puede ser cubierto con el operador de acceso seguro junto al operador de casting, es decir, la construcción as?. Este evita excepciones de casting si los tipos son diferentes.

Toma como ejemplo la expresión movable as? Bike, si movable es de tipo Bike, entonces se consume el casting movable as Bike.

Si movable no es tipo Bike, entonces el resultado será null. Esto podemos verlo cuando pasamos una instancia de Person() en showBike():

fun main() {
    showBike(Person(54.0))
}

fun showBike(movable: Movable){
    val bike = movable as? Bike
    print(bike?.calculateSpeed())
}

El resultado será null al usar el operador de casting seguro as?, lo que nos evita una excepción del tipo ClassCastException.

Únete Al Discord De Develou

Si tienes problemas con el código de este tutorial, preguntas, recomendaciones o solo deseas discutir sobre desarrollo Android conmigo y otros desarrolladores, únete a la comunidad de Discord de Develou y siéntete libre de participar como gustes. ¡Te espero!