Saltar al contenido principal

⚠️ Manejo de Excepciones

📖 ¿Qué es una Excepción?

Una excepción (Exception) es un error que ocurre durante la ejecución del programa. ¡Si maneja las excepciones correctamente, su programa funcionará de manera estable sin detenerse!

💡 try-catch Básico

Uso Básico

fun main() {
try {
val result = 10 / 0 // ¡Ocurre un error!
println(result)
} catch (e: Exception) {
println("Error ocurrido: ${e.message}")
}
// Error ocurrido: / by zero

println("¡El programa continúa ejecutándose!")
}

Manejo de Excepciones Específicas

fun main() {
try {
val text = "abc"
val number = text.toInt() // NumberFormatException
} catch (e: NumberFormatException) {
println("No se puede convertir a número: ${e.message}")
} catch (e: Exception) {
println("Error desconocido: ${e.message}")
}
}

finally

fun main() {
try {
println("Iniciando tarea")
// Realizar tarea
} catch (e: Exception) {
println("Error ocurrido")
} finally {
println("Trabajo de limpieza (siempre se ejecuta)")
}
}

🎯 Ejemplos Prácticos

Conversión Segura a Número

fun safeToInt(text: String): Int? {
return try {
text.toInt()
} catch (e: NumberFormatException) {
null
}
}

fun main() {
println(safeToInt("123")) // 123
println(safeToInt("abc")) // null

val age = safeToInt("25") ?: 0
println("Edad: $age")
}

Simulación de Lectura de Archivo

class FileReader {
fun readFile(filename: String): String {
if (filename.isEmpty()) {
throw IllegalArgumentException("El nombre del archivo está vacío")
}
if (!filename.endsWith(".txt")) {
throw IllegalArgumentException("Solo se admiten archivos de texto")
}
return "Contenido del archivo: $filename"
}
}

fun main() {
val reader = FileReader()

try {
val content = reader.readFile("data.txt")
println(content)
} catch (e: IllegalArgumentException) {
println("Error: ${e.message}")
}

try {
reader.readFile("")
} catch (e: IllegalArgumentException) {
println("Error: ${e.message}")
// Error: El nombre del archivo está vacío
}
}

Validación de Entrada de Usuario

data class User(val name: String, val age: Int)

fun createUser(name: String, age: Int): User {
require(name.isNotBlank()) { "El nombre es obligatorio" }
require(age > 0) { "La edad debe ser positiva" }
require(age < 150) { "La edad es demasiado alta" }

return User(name, age)
}

fun main() {
try {
val user1 = createUser("Juan Pérez", 25)
println(user1)

val user2 = createUser("", 25) // ¡Error!
} catch (e: IllegalArgumentException) {
println("Validación fallida: ${e.message}")
// Validación fallida: El nombre es obligatorio
}
}

Procesamiento de Respuesta de API

sealed class Result<out T> {
data class Success<T>(val data: T) : Result<T>()
data class Error(val exception: Exception) : Result<Nothing>()
}

fun divide(a: Int, b: Int): Result<Int> {
return try {
Result.Success(a / b)
} catch (e: ArithmeticException) {
Result.Error(e)
}
}

fun main() {
val result1 = divide(10, 2)
when (result1) {
is Result.Success -> println("Resultado: ${result1.data}")
is Result.Error -> println("Error: ${result1.exception.message}")
}

val result2 = divide(10, 0)
when (result2) {
is Result.Success -> println("Resultado: ${result2.data}")
is Result.Error -> println("Error: ${result2.exception.message}")
}
}

🔍 Usar como Expresión

try como Valor

fun main() {
val number = try {
"123".toInt()
} catch (e: NumberFormatException) {
0 // Valor predeterminado
}

println(number) // 123

val invalid = try {
"abc".toInt()
} catch (e: NumberFormatException) {
-1
}

println(invalid) // -1
}

Encadenamiento

fun safeDivide(a: Int, b: Int): Int? {
return try {
a / b
} catch (e: ArithmeticException) {
null
}
}

fun main() {
val result = safeDivide(10, 2)
?.let { it * 2 }
?.let { it + 5 }
?: 0

println(result) // 15 (10/2 * 2 + 5)
}

🛡️ Escribir Código Seguro

require vs check

fun processOrder(quantity: Int, price: Int) {
// Validación de entrada
require(quantity > 0) { "La cantidad debe ser positiva" }
require(price > 0) { "El precio debe ser positivo" }

val total = quantity * price

// Validación de estado
check(total < 1000000) { "El total es demasiado grande" }

println("Pedido completado: ${total} pesos")
}

fun main() {
try {
processOrder(10, 5000) // Éxito
processOrder(-1, 5000) // Falla require
} catch (e: IllegalArgumentException) {
println("Error de entrada: ${e.message}")
} catch (e: IllegalStateException) {
println("Error de estado: ${e.message}")
}
}

checkNotNull

fun processName(name: String?) {
val validName = checkNotNull(name) { "El nombre es null" }
println("Procesando: $validName")
}

fun main() {
try {
processName("Juan Pérez") // Éxito
processName(null) // Falla
} catch (e: IllegalStateException) {
println("Error: ${e.message}")
}
}

runCatching

¡Método conveniente disponible desde Kotlin 1.3!

fun main() {
// Usando runCatching
val result = runCatching {
"123".toInt()
}

result
.onSuccess { println("Éxito: $it") }
.onFailure { println("Falla: ${it.message}") }

// getOrNull
val number = runCatching { "abc".toInt() }.getOrNull()
println(number) // null

// getOrDefault
val safe = runCatching { "abc".toInt() }.getOrDefault(0)
println(safe) // 0
}

🎯 Patrones Prácticos

Lógica de Reintento

fun <T> retry(times: Int, block: () -> T): T? {
repeat(times) { attempt ->
try {
return block()
} catch (e: Exception) {
println("Intento ${attempt + 1} fallido: ${e.message}")
if (attempt == times - 1) throw e
}
}
return null
}

fun main() {
var count = 0

try {
val result = retry(3) {
count++
if (count < 3) {
throw Exception("Todavía no está listo")
}
"¡Éxito!"
}
println(result)
} catch (e: Exception) {
println("Fallo final")
}
}

Limpieza de Recursos

class Resource : AutoCloseable {
init {
println("Recurso creado")
}

fun use() {
println("Usando recurso")
}

override fun close() {
println("Limpieza de recurso")
}
}

fun main() {
try {
Resource().use { resource ->
resource.use()
// close() se llama automáticamente
}
} catch (e: Exception) {
println("Error ocurrido")
}
}

🤔 Preguntas Frecuentes

P1. ¿Cuándo se usa throw?

R: ¡Cuando se genera una excepción explícitamente!

fun validateAge(age: Int) {
if (age < 0) {
throw IllegalArgumentException("La edad no puede ser negativa")
}
if (age > 150) {
throw IllegalArgumentException("La edad es demasiado alta")
}
}

P2. ¿Exception vs RuntimeException?

R: ¡Kotlin no hace distinción!

// En Java hay distinción entre checked/unchecked
// Kotlin trata todas como unchecked (no requiere declaración throws)

fun divide(a: Int, b: Int): Int {
return a / b // ¡No se requiere declaración throws!
}

P3. ¿Excepciones personalizadas?

R: ¡Herede de Exception!

class InvalidEmailException(message: String) : Exception(message)

fun validateEmail(email: String) {
if (!email.contains("@")) {
throw InvalidEmailException("Email no válido: $email")
}
}

fun main() {
try {
validateEmail("invalid")
} catch (e: InvalidEmailException) {
println(e.message)
}
}

🎬 Conclusión

¡Cree programas estables con el manejo de excepciones!

Resumen clave:
✅ Manejar excepciones con try-catch
✅ Validar con require/check
✅ Simplificar con runCatching
✅ Devolver valores como expresión
✅ Trabajo de limpieza con finally

Siguiente paso: ¡Aprenda a manejar archivos en Entrada/Salida de Archivos!