Modding d'Hytale
Bases de Java

12 - Gestion des Exceptions

Apprenez à gérer les erreurs avec grâce dans vos programmes Java.

Les exceptions sont des erreurs qui arrivent pendant que votre programme est exécuté. Au lieu de crasher, vous pouvez "attraper" (catch) ces erreurs et les gérer avec grâce.

Qu'est-ce qu'une Exception ?

Une exception est un événement qui perturbe le flux normal de votre programme :

String text = null;
System.out.println(text.length());  // NullPointerException - crash !

Sans gestion, cela ferait crasher votre mod et casserait le jeu !

Blocs d'instructions Try-Catch

Utilisez try-catch pour gérer les exceptions :

try {
    // Code qui pourrait lancer une exception
    String text = null;
    System.out.println(text.length());
} catch (NullPointerException e) {
    // Code pour gérer l'erreur
    System.out.println("Erreur : la variable text était null !");
}

System.out.println("Le programme continue...");
Comment Try-Catch fonctionne
  1. Le code dans le bloc try s'exécute normalement
  2. Si une exception arrive, l'exécution passe dans le bloc catch
  3. Après le bloc catch, le programme continue normalement
  4. Si aucune exception n'arrive, le bloc catch est ignoré
try {
    // Essaie de faire quelque chose de risqué
    int result = 10 / 0;  // Division par zéro !
} catch (ArithmeticException e) {
    // Gère l'erreur
    System.out.println("On ne peut pas diviser par zéro !");
}
// Le programme continue de s'exécuter

Les types courants d'Exception

NullPointerException

Tentative d'accéder aux méthodes/propriétés sur des objets null :

String name = null;

try {
    int length = name.length();
} catch (NullPointerException e) {
    System.out.println("name est null !");
}

ArrayIndexOutOfBoundsException

Tentative d'accéder à un indice de tableau invalide :

int[] numbers = { 1, 2, 3 };

try {
    int value = numbers[10];  // L'indice 10 n'existe pas !
} catch (ArrayIndexOutOfBoundsException e) {
    System.out.println("Indice de tableau invalide !");
}

NumberFormatException

Tentative de convertir un String invalide en nombre :

try {
    int number = Integer.parseInt("abc");  // Pas un nombre !
} catch (NumberFormatException e) {
    System.out.println("Format de nombre invalide !");
}

ArithmeticException

Les erreurs mathématiques comme la division par zéro :

try {
    int result = 10 / 0;
} catch (ArithmeticException e) {
    System.out.println("Erreur mathématique !");
}

Plusieurs blocs Catch

Gérer différentes exceptions différemment :

String input = "abc";

try {
    int number = Integer.parseInt(input);
    int result = 100 / number;
    System.out.println(result);
} catch (NumberFormatException e) {
    System.out.println("Nombre invalide !");
} catch (ArithmeticException e) {
    System.out.println("Impossible de diviser par zéro !");
}

Attraper plusieurs types d'exception

Attrapez plusieurs exceptions dans un seul bloc :

try {
    // Code risqué
} catch (NumberFormatException | ArithmeticException e) {
    System.out.println("Une erreur relative aux mathématiques est arrivée !");
}

Le bloc Finally

Du code qui s'exécute toujours, qu'une exception arrive ou non :

try {
    System.out.println("Ouverture du fichier...");
    // Code qui peut échouer
} catch (Exception e) {
    System.out.println("Erreur : " + e.getMessage());
} finally {
    System.out.println("Fermeture du fichier...");
    // Ceci s'exécute TOUJOURS - pratique pour le nettoyage
}
Quand utiliser Finally

Utilisez finally pour des tâches de nettoyage qui doivent toujours se faire :

  • Fermer des fichiers
  • Libérer des ressources
  • Sauvegarder des données
  • Remplir le Log
FileReader file = null;
try {
    file = new FileReader("data.txt");
    // Lis le fichier
} catch (Exception e) {
    System.out.println("Erreur en lisant le fichier");
} finally {
    if (file != null) {
        file.close();  // Ferme toujours le fichier !
    }
}

Obtenir des informations des Exceptions

L'objet exception contient des informations utiles

try {
    int result = Integer.parseInt("xyz");
} catch (NumberFormatException e) {
    System.out.println("Message : " + e.getMessage());
    System.out.println("Type : " + e.getClass().getName());
    e.printStackTrace();  // Afficher tous les détails de l'erreur
}

Exemples pratiques

Input du joueur sécurisé

import java.util.Scanner;

public class PlayerInput {
    public static int getPlayerChoice(Scanner scanner) {
        while (true) {
            try {
                System.out.print("Entrez votre choix (1-5): ");
                String input = scanner.nextLine();
                int choice = Integer.parseInt(input);
                
                if (choice < 1 || choice > 5) {
                    System.out.println("Entrez un chiffre entre 1 et 5 s'il vous plaît");
                    continue;
                }
                
                return choice;
            } catch (NumberFormatException e) {
                System.out.println("Entrée invalide, entrez un chiffre s'il vous plaît.");
            }
        }
    }
    
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int choice = getPlayerChoice(scanner);
        System.out.println("Vous avez choisi : " + choice);
    }
}

Mise à jour de la durabilité d'un objet sécurisée

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("Les dégâts ne peuvent pas être négatifs !");
            }
            
            durability -= amount;
            
            if (durability < 0) {
                durability = 0;
            }
            
            System.out.println(name + " durabilité : " + durability + "/" + maxDurability);
            
            if (durability == 0) {
                System.out.println(name + " s'est cassé !");
            }
        } catch (IllegalArgumentException e) {
            System.out.println("Erreur : " + e.getMessage());
        }
    }
}

Chargement de configuration sécurisé

import java.util.HashMap;

public class ConfigLoader {
    private HashMap<String, String> settings;
    
    public ConfigLoader() {
        this.settings = new HashMap<>();
        loadDefaults();
    }
    
    private void loadDefaults() {
        settings.put("joueursMax", "10");
        settings.put("difficulté", "normale");
        settings.put("pvpActivé", "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("Nombre invalide pour " + key + ", utilisation de la valeur par défaut");
            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("Booléen invalide pour " + key + ", utilisation de la valeur par défaut");
            return defaultValue;
        }
    }
    
    public void setSetting(String key, String value) {
        if (key == null || value == null) {
            System.out.println("Clé et valeur ne peuvent pas être null !");
            return;
        }
        settings.put(key, value);
    }
}

Accès à un tableau sécurisés

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("Emplacement invalide : " + slot);
            }
            
            items[slot] = item;
            System.out.println(item + " placé dans l'emplacement " + slot);
            return true;
        } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println("Erreur : " + e.getMessage());
            return false;
        }
    }
    
    public String getItem(int slot) {
        try {
            return items[slot];
        } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println("Emplacement invalide : " + slot);
            return null;
        }
    }
}

Parseur de commande avec gestion d'erreur

public class CommandParser {
    public static void parseCommand(String command) {
        try {
            if (command == null || command.trim().isEmpty()) {
                throw new IllegalArgumentException("Une commande ne peut pas être vide !");
            }
            
            String[] parts = command.split(" ");
            
            if (parts.length < 2) {
                throw new IllegalArgumentException("Format de commande invalide !");
            }
            
            String action = parts[0];
            String target = parts[1];
            
            switch (action) {
                case "give":
                    if (parts.length < 4) {
                        throw new IllegalArgumentException("Usage : give <player> <item> <amount>");
                    }
                    String item = parts[2];
                    int amount = Integer.parseInt(parts[3]);
                    
                    if (amount <= 0) {
                        throw new IllegalArgumentException("La quantité doit être positive !");
                    }
                    
                    System.out.println("Don de " + amount + " " + item + " à " + target);
                    break;
                    
                case "tp":
                    if (parts.length < 5) {
                        throw new IllegalArgumentException("Usage : 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("Téléporte " + target + " en " + x + ", " + y + ", " + z);
                    break;
                    
                default:
                    throw new IllegalArgumentException("Commande inconnue : " + action);
            }
        } catch (NumberFormatException e) {
            System.out.println("Erreur : nombre invalide dans la commande !");
        } catch (IllegalArgumentException e) {
            System.out.println("Erreur : " + e.getMessage());
        } catch (Exception e) {
            System.out.println("Erreur inattendue : " + e.getMessage());
        }
    }
    
    public static void main(String[] args) {
        parseCommand("give Steve diamond 5");
        parseCommand("give Steve sword abc");  // Nombre invalide
        parseCommand("tp Alice 10 64 20");
        parseCommand("unknown command");       // Commande inconnue
    }
}

Lancer des Exceptions

Vous pouvez lancer vos propres exceptions :

public class Player {
    private int health;
    
    public void setHealth(int health) {
        if (health < 0) {
            throw new IllegalArgumentException("La vie ne peut pas être négative !");
        }
        if (health > 100) {
            throw new IllegalArgumentException("La vie ne peut pas excéder 100 !");
        }
        this.health = health;
    }
}
Quand lancer des Exceptions

Lancez des exceptions quand :

  • Une méthode reçoit une entrée invalide
  • Une opération ne peut pas être réalisée
  • Des préconditions ne sont pas réunies
  • Quelque chose d'inattendu arrive
public void damagePlayer(int damage) {
    if (damage < 0) {
        throw new IllegalArgumentException("Les dégâts doivent être positifs !");
    }
    // Appliquer les dégâts
}

public Item getItem(int slot) {
    if (slot < 0 || slot >= inventory.length) {
        throw new IndexOutOfBoundsException("Emplacement d'inventaire invalide !");
    }
    return inventory[slot];
}

Les Exceptions vérifiées vs non vérifiées

Types d'Exceptions

Exceptions non vérifiées (RuntimeException) :

  • N'ont pas besoin d'être attrapées
  • Généralement des erreurs de programmation
  • Exemples : NullPointerException, ArrayIndexOutOfBoundsException

Exceptions vérifiées :

  • Doivent être attrapées ou déclarées
  • Généralement les erreurs externes (fichiers, réseau)
  • Exemples : IOException, FileNotFoundException
// Non vérifiée - pas besoin d'attraper
int result = 10 / 0;  // ArithmeticException

// Vérifiée - doit attraper ou déclarer
try {
    FileReader file = new FileReader("data.txt");  // IOException
} catch (IOException e) {
    // Gérer l'erreur
}

Exercices pratiques

  1. Calculateur de division sécurisé : créez une méthode qui divise deux nombres. Gérez la division par zéro et les entrées invalides avec grâce.

  2. Setter de niveau de joueur : créez une méthode qui définit le niveau du joueur (1-100). Lancez une exception si invalide, et gérez-le dans le main.

  3. Accès à un tableau sécurisés : créez une méthode qui accède aux éléments d'un tableau de manière sécurisée. Renvoie null si l'index est invalide au lieu de crasher.

  4. Parseur de configuration : lisez un String de configuration comme "joueursMax=10" et parsez-le. Gérez les formats invalides avec grâce.

  5. Vérificateur de nom de fichier : créez une méthode qui vérifie si un nom de fichier est valide (pas de caractères spéciaux, pas vide). Lancez des exceptions pour les noms invalides.