En este tutorial aprenderás sobre el lanzamiento y manejo de excepciones en Kotlin con el objetivo de conducir el flujo de tus programas a un estado consistente. Para ello verás el uso de las sentencias throw
y try
.
Lanzar Excepciones En Kotlin
Para lanzar lanzar objetos de excepción en Kotlin, usa la expresión throw
seguido de la instanciación del tipo. Al igual que en Java, la declaración del tipo debe tener como supertipo a la interfaz Throwable
.
Considera un programa sencillo que lee por teclado un texto escrito por el usuario, donde se desea contar la cantidad de dígitos que el texto tenga.
En el caso de que el texto que introduzca sea nulo o un espacio en blanco, entonces lanzaremos una excepción que indique el incumplimiento de esta precondición.
fun main() {
print("Escribe el texto: ")
val userInput = readLine()
println("Cantidad de digitos: ${countDigits(userInput)}")
}
fun countDigits(userInput: String?): Int {
if (userInput.isNullOrBlank()) {
throw IllegalArgumentException("Entrada inválida, la palabra debe tener al menos un carácter")
}
return userInput.count(Char::isDigit)
}
Salida al no incluir texto:
Escribe el texto:
Exception in thread "main" java.lang.IllegalArgumentException: Entrada inválida, la palabra debe tener al menos un carácter
Como habíamos especificado, la construcción throw
es una expresión. Esta característica te habilita su uso en otras expresiones.
Por ejemplo, countDigits()
puede ser refactorizada como una función con cuerpo de una sola expresión al indicar a throw
como resultado de la expresión if.
fun countDigits(userInput: String?) =
if (userInput.isNullOrBlank())
throw IllegalArgumentException("Entrada inválida, la palabra debe tener al menos un carácter")
else
userInput.count(Char::isDigit)
Manejar Excepciones En Kotlin
Al igual que en Java, Kotlin te permite capturar excepciones con la expresión try..catch..finally
.
Pon en el bloque try
el código que es propenso a lanzar excepciones y luego añade bloques catch
que verifiquen la aplicabilidad de un subtipo de excepción.
El bloque finally
se ejecuta luego de que se aplique o no algún bloque catch
. Normalmente aquí liberas los recursos que has tomado del sistema y limpias referencias para evitar fugas de memoria.
Tomemos como ejemplo la conversión de un String
a Double
. Supongamos que deseamos crear una función de extensión que envuelva a la función String.toDouble()
para que retorne un valor por defecto en caso de que se lance una excepción del tipo NumberFormatException
.
fun main() {
println("5.3".toDoubleOrDefault(1.0))
println("5.".toDoubleOrDefault(1.0))
println(".3".toDoubleOrDefault(1.0))
println("dos".toDoubleOrDefault(1.0))
}
fun String.toDoubleOrDefault(defaultValue: Double): Double {
return try {
toDouble()
} catch (e: NumberFormatException) {
defaultValue
}
}
Salida:
5.3
5.0
0.3
1.0
Como ves, el bloque catch
al atrapar un error de parsing del string debido a un formato incorrecto, retorna el valor por defecto que entra como argumento de toDoubleOrDefault()
.
Por supuesto esta función también puede ser escrita con cuerpo de expresión:
fun String.toDoubleOrDefault(defaultValue: Double) = try {
toDouble()
} catch (e: NumberFormatException) {
defaultValue
}
Excepciones Marcadas
Kotlin no diferencia entre excepciones marcadas y no marcadas (checked y unchecked). Las funciones no deben especificar explícitamente que tipo de excepción lanzaran (como el uso de la cláusula throws
en la firmas de métodos en Java), ni tampoco se te obliga a atrapar excepciones.
Esto quiere decir que cuando escribes sentencias que invoquen operaciones que lancen excepciones marcadas desde el SDK de Java, el compilador de Kotlin no te resaltará su uso sin bloque try..catch
. Por ejemplo, como intentar abrir un archivo que no existe.
fun main() {
val stream = FileInputStream("archivo inválido")
}
Aunque este constructor hace explicito que lanzará una excepción marcada del tipo FileNotFoundException
, no recibimos como exigencia controlarla. Obviamente al ejecutar esta sentencia tendremos el aviso esperado:
Exception in thread "main" java.io.FileNotFoundException: archivo inválido (El sistema no puede encontrar el archivo especificado)
Esto no significa que las ignoraremos, el manejo de errores debe seguir siendo parte de tu esquema. Significa que nos permite evitar la propagación de cambios en cadena entre diferentes firmas de funciones, cuando hay modificaciones de excepciones.