12 - Gestión de excepciones
Aprende a gestionar los errores con elegancia en sus programas Java.
Las excepciones son errores que se producen mientras se ejecuta el programa. En lugar de bloquearse, puede «detectar» estos errores y gestionarlos con elegancia.
¿Qué es una excepción?
Una excepción es un evento que interrumpe el flujo normal de tu programa:
Si no se controla, ¡esto puede provocar que tu mod falle y el juego se rompa!
Bloques Try-Catch
Use "try-catch" para gestionar excepciones:
try {
// Código que podría lanzar una excepción
String text = null;
System.out.println(text.length());
} catch (NullPointerException e) {
// Código para manejar el error
System.out.println("Error: el texto era null!");
}
System.out.println("El programa continúa...");- El código en el bloque 'try' se ejecuta como de costumbre
- Si ocurre una excepción, la ejecución salta al bloque
catch - Después del bloque
catch, el programa sigue funcionando - Si no ocurre ninguna excepción, se omite el bloque
catch
try {
// Intenta crear un error
int result = 10 / 0; // Dividir por cero!
} catch (ArithmeticException e) {
// Maneja el error
System.out.println("No puede dividirse por cero!");
}
// El programa sigue funcionandoTipos de excepciones comunes
NullPointerException
Acceder a métodos o propiedades de un objeto que es null:
String name = null;
try {
int length = name.length();
} catch (NullPointerException e) {
System.out.println("Name es nulo (null)!");
}ArrayIndexOutOfBoundsException
Acceder a un índice de un arreglo que no es válido:
int[] numbers = {1, 2, 3};
try {
int value = numbers[10]; // ¡Index 10 no existe!
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("¡Índice de array inválido!");
}NumberFormatException
Convierte cadenas inválidas en números:
try {
int number = Integer.parseInt("abc"); // ¡No es un número!
} catch (NumberFormatException e) {
System.out.println("¡Formato de número inválido!");
}ArithmeticException
Errores matemáticos como la división por cero:
try {
int result = 10 / 0;
} catch (ArithmeticException e) {
System.out.println("¡Error matemático!");
}Bloques de captura múltiples
Gestiona diferentes excepciones de distinta manera:
String input = "abc";
try {
int number = Integer.parseInt(input);
int result = 100 / number;
System.out.println(result);
} catch (NumberFormatException e) {
System.out.println("¡Número inválido!");
} catch (ArithmeticException e) {
System.out.println("¡No puede dividirse por cero!");
}Captura de múltiples tipos de excepciones
Captura múltiples excepciones en un bloque:
try {
// Algún código que puede causar errores
} catch (NumberFormatException | ArithmeticException e) {
System.out.println("¡Ocurrió un error relacionado con operaciones matemáticas!");
}El bloque "Finally"
Código que siempre se ejecuta, independientemente de que se produzca una excepción o no:
try {
System.out.println("Abriendo archivo...");
// Código que podría fallar
} catch (Exception e) {
System.out.println("Error: " + e.getMessage());
} finally {
System.out.println("Cerrando archivo...");
// Esto SIEMPRE se ejecuta — es ideal para limpieza de recursos
}Usa "finally" para tareas de limpieza que siempre deben ejecutarse:
- Cierre de archivos
- Liberación de recursos
- Guardar datos
- Registro
FileReader file = null;
try {
file = new FileReader("data.txt");
// Lee el archivo
} catch (Exception e) {
System.out.println("Error al leer el archivo");
} finally {
if (file != null) {
file.close(); // ¡Siempre cerrar el archivo!
}
}Obtener información de una excepción
El objeto de la excepción contiene información útil:
try {
int result = Integer.parseInt("xyz");
} catch (NumberFormatException e) {
System.out.println("Mensaje: " + e.getMessage());
System.out.println("Tipo: " + e.getClass().getName());
e.printStackTrace(); // Imprime todos los detalles del error
}Ejemplos Prácticos
Entrada segura del jugador
import java.util.Scanner;
public class PlayerInput {
public static int getPlayerChoice(Scanner scanner) {
while (true) {
try {
System.out.print("Ingresa una elección (1-5): ");
String input = scanner.nextLine();
int choice = Integer.parseInt(input);
if (choice < 1 || choice > 5) {
System.out.println("Por favor, introduzca un número entre 1 y 5");
continue;
}
return choice;
} catch (NumberFormatException e) {
System.out.println("¡La entrada no es válida! Por favor, introduzca un número.");
}
}
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int choice = getPlayerChoice(scanner);
System.out.println("Tu elección: " + choice);
}
}Actualización Segura de la Durabilidad de Ítems
public class Item {
private String name;
private int durability;
private int maxDurability;
public Item(String name, int maxDurability) {
this.name = name;
this.durability = maxDurability;
this.maxDurability = maxDurability;
}
public void damage(int amount) {
try {
if (amount < 0) {
throw new IllegalArgumentException("¡El daño no puede ser negativo!");
}
durability -= amount;
if (durability < 0) {
durability = 0;
}
System.out.println(name + " durabilidad: " + durability + "/" + maxDurability);
if (durability == 0) {
System.out.println(name + " se rompió!");
}
} catch (IllegalArgumentException e) {
System.out.println("Error: " + e.getMessage());
}
}
}Carga segura de configuración
import java.util.HashMap;
public class ConfigLoader {
private HashMap<String, String> settings;
public ConfigLoader() {
this.settings = new HashMap<>();
loadDefaults();
}
private void loadDefaults() {
settings.put("maxPlayers", "10");
settings.put("difficulty", "normal");
settings.put("pvpEnabled", "true");
}
public int getIntSetting(String key, int defaultValue) {
try {
String value = settings.get(key);
if (value == null) {
return defaultValue;
}
return Integer.parseInt(value);
} catch (NumberFormatException e) {
System.out.println("Número inválido para " + key + ", usando valor por defecto");
return defaultValue;
}
}
public boolean getBooleanSetting(String key, boolean defaultValue) {
try {
String value = settings.get(key);
if (value == null) {
return defaultValue;
}
return Boolean.parseBoolean(value);
} catch (Exception e) {
System.out.println("Booleano inválido para " + key + ", usando valor por defecto");
return defaultValue;
}
}
public void setSetting(String key, String value) {
if (key == null || value == null) {
System.out.println("¡La clave y el valor no pueden ser null!");
return;
}
settings.put(key, value);
}
}Acceso seguro a arreglos
public class Inventory {
private String[] items;
public Inventory(int size) {
this.items = new String[size];
}
public boolean setItem(int slot, String item) {
try {
if (slot < 0 || slot >= items.length) {
throw new ArrayIndexOutOfBoundsException("Slot inválido: " + slot);
}
items[slot] = item;
System.out.println("Colocado " + item + " en el slot " + slot);
return true;
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("Error: " + e.getMessage());
return false;
}
}
public String getItem(int slot) {
try {
return items[slot];
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("Slot inválido: " + slot);
return null;
}
}
}Analizador de comandos con gestión de errores
public class CommandParser {
public static void parseCommand(String command) {
try {
if (command == null || command.trim().isEmpty()) {
throw new IllegalArgumentException("¡El comando no puede estar vacío!");
}
String[] parts = command.split(" ");
if (parts.length < 2) {
throw new IllegalArgumentException("¡El formato del comando es invalido!");
}
String action = parts[0];
String target = parts[1];
switch (action) {
case "give":
if (parts.length < 4) {
throw new IllegalArgumentException("Forma de utilización: give <player> <item> <amount>");
}
String item = parts[2];
int amount = Integer.parseInt(parts[3]);
if (amount <= 0) {
throw new IllegalArgumentException("¡La cantidad debe ser positiva!");
}
System.out.println("Dando " + amount + " " + item + " a " + target);
break;
case "tp":
if (parts.length < 5) {
throw new IllegalArgumentException("Forma de utilización: tp <player> <x> <y> <z>");
}
int x = Integer.parseInt(parts[2]);
int y = Integer.parseInt(parts[3]);
int z = Integer.parseInt(parts[4]);
System.out.println(target + " Será teletransportado a " + x + ", " + y + ", " + z);
break;
default:
throw new IllegalArgumentException("Comando desconocido: " + action);
}
} catch (NumberFormatException e) {
System.out.println("Error: ¡Número inválido en el comando!");
} catch (IllegalArgumentException e) {
System.out.println("Error: " + e.getMessage());
} catch (Exception e) {
System.out.println("Error inesperado: " + e.getMessage());
}
}
public static void main(String[] args) {
parseCommand("give Steve diamond 5");
parseCommand("give Steve sword abc"); // Número inválido
parseCommand("tp Alice 10 64 20");
parseCommand("unknown command"); // Comando desconocido
}
}Lanzar Excepciones
Tú puedes lanzar tus propias excepciones:
public class Player {
private int health;
public void setHealth(int health) {
if (health < 0) {
throw new IllegalArgumentException("La salud no puede ser negativa!");
}
if (health > 100) {
throw new IllegalArgumentException("La salud no puede exceder de 100!");
}
this.health = health;
}
}Lanzar excepciones cuando:
- El método recibe una entrada inválida
- La operación no puede ser completada
- Las condiciones previas no se cumplen
- Ocurre algo inesperado
public void damagePlayer(int damage) {
if (damage < 0) {
throw new IllegalArgumentException("El daño debe tener valores positivos!");
}
// Aplicar daño
}
public Item getItem(int slot) {
if (slot < 0 || slot >= inventory.length) {
throw new IndexOutOfBoundsException("Slot inválido de inventario!");
}
return inventory[slot];
}Excepciones revisadas vs no revisadas
Excepciones sin comprobar (RuntimeException):
- No es necesario manejarla con "catch"
- Normalmente errores de programación
- Ejemplos: NullPointerException, ArrayIndexOutOfBoundsException
Excepciones checkeadas:
- Deben ser declaradas
- Normalmente errores externos (archivos, red)
- Ejemplos: IOException, FileNotFoundException
// Uncheckeada - no se necesita un catch
int result = 10 / 0; // ArithmeticException
// Checkeada - se precisa de un catch o declarar
try {
FileReader file = new FileReader("data.txt"); // IOException
} catch (IOException e) {
// Trata el error
}Ejercicios de Práctica
-
Calculadora de División Segura: Crear un método que divida dos números. Manejar la división entre cero y las entradas inválidas de forma adecuada.
-
Configurador de Nivel del Jugador: Crear un método que establezca el nivel del jugador (1-100). Lanzar una excepción si el valor es inválido y manejarla en "main".
-
Acceso seguro a arreglos: Crear un método que acceda de forma segura a los elementos de un arreglo. Devuelve null si el índice no es válido, en lugar de provocar un fallo.
-
Analizador de configuración: Leer una cadena de configuración como "maxPlayers=10" y procesarlo en datos utilizables. Maneja los formatos inválidos de forma adecuada.
-
Verificador de nombre de archivo: Crear un método que verifica si el nombre del archivo es válido (sin caracteres especiales y que no este vacío) Lanzar excepciones por nombres inválidos.