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 comoopen
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!