Excepciones En Kotlin

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.

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