Hytale Modding
Conceptos básicos de Java

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...");
Cómo funciona Try-Catch
  1. El código en el bloque 'try' se ejecuta como de costumbre
  2. Si ocurre una excepción, la ejecución salta al bloque catch
  3. Después del bloque catch, el programa sigue funcionando
  4. 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 funcionando

Tipos 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
}
Cuando utilizar "Finally"

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;
    }
}
Cuando lanzar Excepciones

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

Tipos de Excepciones

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

  1. 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.

  2. 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".

  3. 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.

  4. 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.

  5. 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.