Manejo de Excepciones en Kotlin

Manejo de Excepciones en Kotlin

Una excepción es un error o un imprevisto que requiere un manejo especial. Si el problema no se puede resolver, el programa termina abruptamente, pero si conseguimos anticipar y detectar la excepción podremos manejar la situación.

Este manejo de excepciones nos permite controlar ciertos errores ocasionados durante la ejecución de un programa. Así, cuando ocurre cierto tipo de error, el sistema reacciona ejecutando un fragmento de código que resuelve la situación, por ejemplo retornando un mensaje de error o devolviendo un valor por defecto.

En definitiva, el manejo de excepciones se refiere a la capacidad de abordar una excepción que podría ocurrir, lo que permite crear aplicaciones tolerantes a potenciales errores y suficientemente robustas para poder seguir ejecutándose sin interrumpirse por el problema.

Existen diversos tipos de excepciones y formas para manejarlas, pero en Kotlin todas las clases de excepción son descendientes de la clase Throwable, y entonces podemos lanzar un objeto de excepción con la expresión throw:
throw Exception("Error detectado!")

throw es una expresión y puede combinarse con otras expresiones:
fun checkLetra(letra: Char) {
    val resultado =
        if (letra in 'a'..'f')
            letra
        else
            throw IllegalArgumentException("La letra debe estar entre a y f")
    println(resultado)
}

fun main() {
    checkLetra('d')
    checkLetra('s')
}
d
Exception in thread "main" java.lang.IllegalArgumentException: La letra debe estar entre a y f
    at MainKt.checkLetra(main.kt:6)
    at MainKt.main(main.kt:12)
    at MainKt.main(main.kt)

También podríamos usarla como parte de una expresión Elvis:
val nombre = persona.nombre ?: throw IllegalArgumentException("Nombre requerido")

La expresión throw devuelve un valor de tipo Nothing que es un tipo especial sin valor que se utiliza para marcar ubicaciones de código que nunca se alcanzan, por lo que también se puede usar directamente, por ejemplo, como tipo de retorno en una función para indicar que nunca devuelva nada.
fun mostrarError(mensaje: String): Nothing {
    println(mensaje)
    throw Exception(mensaje)
}

Y para atrapar una excepción, usamos la expresión try. try requiere al menos un bloque catch (pueden haber varios) o un bloque finally (solo puede haber uno):
try {
    // intenta algún código
}
catch (e: SomeException) {
    // captura una excepción
}
finally {
    // bloque opcional que siempre se ejecuta
}

Por ejemplo:
fun manejoEx() {
    try {
        throw Exception("Excepción detectada!")
    } catch (e: Exception) {
        println("Excepción manejada!")
    } finally {
        println("Dentro del bloque finally")
    }
}
Excepción manejada!
Dentro del bloque finally

try también es una expresión, y por tanto puede tener un valor de retorno:
fun esPositivo(numero: Int) {
    val resultado = try {
        if (numero < 0) {
            throw IllegalArgumentException()
        }
        true
    } catch (e: IllegalArgumentException) {
        false
    }
    println(resultado)
}

fun main() {
    esPositivo(2) // true
    esPositivo(-1) // false
}

Otro ejemplo:
import java.lang.Integer.parseInt

fun main() {

    print("Un número entero: ")
    val input = readLine()

    val a: Int? = try {
        parseInt(input)
    } catch (e: NumberFormatException) {
        null
    }

    println("a = $a")
}
Un número entero: 2
a = 2
Un número entero: abc
a = null

Hay que tener en cuenta que el valor devuelto por la expresión try es la última expresión en ese bloque o en un bloque catch, pero el contenido del bloque finally no afecta al resultado de la expresión.

El bloque catch puede capturar un tipo específico de excepción o cualquier excepción con la clase base de todas las excepciones (Exception). También podemos crear una excepción personalizada que se derive de la clase Exception:
class MiExcepcion(msg: String) : Exception(msg)

fun main() {
    throw MiExcepcion("Error!")
}

Otro ejemplo de excepciones personalizadas:
class NombreExcepcion(msg: String) : Exception(msg)

class EdadExcepcion(msg: String) : Exception(msg)

fun main() {

    val nombre: String
    print("Introduce tu nombre: ")
    val str = readLine()
    if (str != "") {
        nombre = str.toString()
    } else {
        throw Exception("¡Error input nombre!")
    }

    val edad: Int
    print("Introduce tu edad: ")
    val num: Int? = readLine()?.toIntOrNull()
    if (num != null) {
        edad = num
    } else {
        throw Exception("¡Eso no es un número entero!")
    }

    inputNombre(nombre)
    inputEdad(edad)
}

fun inputNombre(input: String) {
    try {
        validarNombre(input)
    } catch (e: NombreExcepcion) {
        println(e.message)
    }
}

fun inputEdad(input: Int) {
    try {
        validarEdad(input)
    } catch (e: EdadExcepcion) {
        println(e.message)
    }
}

fun validarNombre(nombre: String) {
    if (nombre.matches(Regex(".*\\d+.*"))) {
        throw NombreExcepcion("Nombre : $nombre - NO VÁLIDO: contiene números.")
    }
    println("Nombre: $nombre - VÁLIDO: no contiene números")
}

fun validarEdad(edad: Int) {
    if (edad < 18) {
        throw NombreExcepcion("Edad: $edad - NO VÁLIDA: Menor de edad.")
    }
    println("Edad: $edad - VÁLIDA: Mayor de edad")
}

El bloque finally contiene código que siempre se ejecuta y suele ser útil para hacer algo de limpieza, como cerrar archivos abiertos o liberar recursos.

Ejemplo: excepción de división entre cero:
fun dividir(x: Int, y: Int) {
    val z = try {
        x / y // si y = 0: ArithmeticException: Division by zero
    } catch (e: ArithmeticException) {
        0 // en caso de division por cero asignamos valor 0 a z
    } finally {
        println("Division Terminada") // siempre se imprime "Division Terminada"
    }
    println("Resultado: $z")
}

fun main() {
    dividir(24, 4)
    dividir(48, 0)
}
Division Terminada
Resultado: 6
Division Terminada
Resultado: 0

Toda excepción tiene un mensaje, un seguimiento de pila y una causa opcional. Se puede acceder al mensaje de la excepción usando la propiedad message del objeto de excepción:
fun main() {
    val c: Int
    try {
        c = 6 / 0
    } catch (e: ArithmeticException) {
        println(e.message) // / by zero
    }
}

El seguimiento de pila (Stack Trace) ayuda a rastrear el fragmento de código que causó la excepción. Se puede acceder al seguimiento de la pila desde el objeto de excepción lanzado en el bloque catch y para imprimir el seguimiento de la pila usamos el método printStackTrace() en el objeto de excepción:
fun main() {
    val test = "Kotlin Doc"
    try {
        test.toInt()
    } catch (e: Exception) {
        e.printStackTrace()
    } finally {
        println("Fin")
    }
}
java.lang.NumberFormatException: For input string: "Kotlin Doc"
    at java.base/java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
    at java.base/java.lang.Integer.parseInt(Integer.java:652)
    at java.base/java.lang.Integer.parseInt(Integer.java:770)
    at MainKt.main(main.kt:4)
    at MainKt.main(main.kt)
Fin

Comentarios

Entradas populares

Recursos gratis para aprender Kotlin

I/O: entrada y salida de datos en consola

Lectura y escritura de archivos