Hytale Modding
Основи Java

12 - Обробка винятків

Дізнайтеся, як коректно обробляти помилки у ваших Java-програмах.

Винятки - це помилки, що виникають під час виконання програми. Замість того, щоб викликати збій, ви можете "перехопити" ці помилки та коректно їх обробити.

Що таке виняток?

Виняток - це подія, що порушує нормальний хід виконання програми:

String text = null;
System.out.println(text.length());  // NullPointerException - збій!

Без обробки це призведе до збою вашої модифікації та порушенню роботи гри!

Блоки try-catch

Використовуйте try-catch для обробки винятків:

try {
    // Код, який може викликати виняток
    String text = null;
    System.out.println(text.length());
} catch (NullPointerException e) {
    // Код для обробки помилки
    System.out.println("Помилка: текст був нульовим!");
}

System.out.println("Програма продовжує роботу...");
Як працює try-catch
  1. Код у блоці try виконується нормально
  2. Якщо виникає виняток, виконання переходить до блоку catch
  3. Після блоку catch програма продовжує виконуватися нормально
  4. Якщо виняток не виникає, блок catch пропускається
try {
    // Спроба зробити щось ризиковане
    int result = 10 / 0;  // Ділення на нуль!
} catch (ArithmeticException e) {
    // Обробка помилки
    System.out.println("Не можна ділити на нуль!");
}
// Програма продовжує виконуватися

Загальні типи винятків

NullPointerException

Доступ до методів/властивостей нульових об'єктів:

String name = null;

try {
    int length = name.length();
} catch (NullPointerException e) {
    System.out.println("Ім'я відсутнє!");
}

ArrayIndexOutOfBoundsException

Доступ до недійсного індексу масиву:

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

try {
    int value = numbers[10];  // Індекс 10 не існує!
} catch (ArrayIndexOutOfBoundsException e) {
    System.out.println("Недійсний індекс масиву!");
}

NumberFormatException

Перетворення недійсних рядків у числа:

try {
    int number = Integer.parseInt("abc");  // Не число!
} catch (NumberFormatException e) {
    System.out.println("Невірний формат числа!");
}

ArithmeticException

Математичні помилки, такі як ділення на нуль:

try {
    int result = 10 / 0;
} catch (ArithmeticException e) {
    System.out.println("Математична помилка!");
}

Кілька блоків catch

Різні винятки обробляються по-різному:

String input = "abc";

try {
    int number = Integer.parseInt(input);
    int result = 100 / number;
    System.out.println(result);
} catch (NumberFormatException e) {
    System.out.println("Невірний номер!");
} catch (ArithmeticException e) {
    System.out.println("Не можна ділити на нуль!");
}

Перехоплення декількох типів винятків

Перехоплення декількох винятків в одному блоці:

try {
    // Деякий ризикований код
} catch (NumberFormatException | ArithmeticException e) {
    System.out.println("Сталася математична помилка!");
}

Блок finally

Код, який завжди виконується, незалежно від того, чи стався виняток:

try {
    System.out.println("Відкриття файлу...");
    // Код, який може завершитися невдачею
} catch (Exception e) {
    System.out.println("Помилка: " + e.getMessage());
} finally {
    System.out.println("Закриття файлу...");
    // Це ЗАВЖДИ виконується - добре підходить для очищення
}
Коли використовувати finally

Використовуйте finally для завдань очищення, які повинні виконуватися завжди:

  • Закриття файлів
  • Звільнення ресурсів
  • Збереження даних
  • Ведення журналу
FileReader file = null;
try {
    file = new FileReader("data.txt");
    // Читання файлу
} catch (Exception e) {
    System.out.println("Помилка під час читання файлу");
} finally {
    if (file != null) {
        file.close();  // Завжди закривайте файл!
    }
}

Отримання інформації про виняток

Об'єкт винятку містить корисну інформацію:

try {
    int result = Integer.parseInt("xyz");
} catch (NumberFormatException e) {
    System.out.println("Повідомлення: " + e.getMessage());
    System.out.println("Тип: " + e.getClass().getName());
    e.printStackTrace();  // Вивести повну інформацію про помилку
}

Практичні приклади

Безпечне введення гравця

import java.util.Scanner;

public class PlayerInput {
    public static int getPlayerChoice(Scanner scanner) {
        while (true) {
            try {
                System.out.print("Введіть номер (1-5): ");
                String input = scanner.nextLine();
                int choice = Integer.parseInt(input);
                
                if (choice < 1 || choice > 5) {
                    System.out.println("Введіть число від 1 до 5");
                    continue;
                }
                
                return choice;
            } catch (NumberFormatException e) {
                System.out.println("Неправильний вхід! Введіть число.");
            }
        }
    }
    
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int choice = getPlayerChoice(scanner);
        System.out.println("Ви вибрали: " + choice);
    }
}

Безпечне оновлення міцності предмета

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("Шкода не може бути негативною!");
            }
            
            durability -= amount;
            
            if (durability < 0) {
                durability = 0;
            }
            
            System.out.println(name + " міцність: " + durability + "/" + maxDurability);
            
            if (durability == 0) {
                System.out.println(name + " зламався!");
            }
        } catch (IllegalArgumentException e) {
            System.out.println("Помилка: " + e.getMessage());
        }
    }
}

Безпечне завантаження конфігурації

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("Неправильне число для " + key + ", використовується значення за замовчуванням");
            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("Недійсний логічний тип для " + key + ", використовується значення за замовчуванням");
            return defaultValue;
        }
    }
    
    public void setSetting(String key, String value) {
        if (key == null || value == null) {
            System.out.println("Ключ і значення не можуть бути нульовими!");
            return;
        }
        settings.put(key, value);
    }
}

Безпечний доступ до масиву

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);
            }
            
            items[slot] = item;
            System.out.println("Поміщено " + item + " у слот " + slot);
            return true;
        } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println("Помилка: " + e.getMessage());
            return false;
        }
    }
    
    public String getItem(int slot) {
        try {
            return items[slot];
        } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println("Невірний слот: " + slot);
            return null;
        }
    }
}

Аналізатор команд з обробкою помилок

public class CommandParser {
    public static void parseCommand(String command) {
        try {
            if (command == null || command.trim().isEmpty()) {
                throw new IllegalArgumentException("Команда не може бути порожньою!");
            }
            
            String[] parts = command.split(" ");
            
            if (parts.length < 2) {
                throw new IllegalArgumentException("Неправильний формат команди!");
            }
            
            String action = parts[0];
            String target = parts[1];
            
            switch (action) {
                case "give":
                    if (parts.length < 4) {
                        throw new IllegalArgumentException("Використання: give <player> <item> <amount>");
                    }
                    String item = parts[2];
                    int amount = Integer.parseInt(parts[3]);
                    
                    if (amount <= 0) {
                        throw new IllegalArgumentException("Кількість повинна бути позитивною!");
                    }
                    
                    System.out.println("Видано " + 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("Переміщуємо " + target + " у " + x + ", " + y + ", " + z);
                    break;
                    
                default:
                    throw new IllegalArgumentException("Невідома команда: " + action);
            }
        } catch (NumberFormatException e) {
            System.out.println("Помилка: Неправильне число в команді!");
        } catch (IllegalArgumentException e) {
            System.out.println("Помилка: " + e.getMessage());
        } catch (Exception e) {
            System.out.println("Несподівана помилка: " + e.getMessage());
        }
    }
    
    public static void main(String[] args) {
        parseCommand("give Steve diamond 5");
        parseCommand("give Steve sword abc");  // Неправильне число
        parseCommand("tp Alice 10 64 20");
        parseCommand("unknown command");       // Невідома команда
    }
}

Генерування винятків

Ви можете генерувати власні винятки:

public class Player {
    private int health;
    
    public void setHealth(int health) {
        if (health < 0) {
            throw new IllegalArgumentException("Здоров'я не може бути негативним!");
        }
        if (health > 100) {
            throw new IllegalArgumentException("Здоров'я не може перевищувати 100!");
        }
        this.health = health;
    }
}
Коли генерувати винятки

Генеруйте винятки, коли:

  • Метод отримує невірні вхідні дані
  • Операція не може бути виконана
  • Не виконані попередні умови
  • Сталося щось несподіване
public void damagePlayer(int damage) {
    if (damage < 0) {
        throw new IllegalArgumentException("Шкода повинна бути позитивною!");
    }
    // Застосування шкоди
}

public Item getItem(int slot) {
    if (slot < 0 || slot >= inventory.length) {
        throw new IndexOutOfBoundsException("Невірний слот інвентарю!");
    }
    return inventory[slot];
}

Перевірені та неперевірені винятки

Типи винятків

Неперевірені винятки (RuntimeException):

  • Не потрібно виловлювати
  • Зазвичай це помилки програмування
  • Приклади: NullPointerException, ArrayIndexOutOfBoundsException

Перевірені винятки:

  • Повинні бути перехоплені або оголошені
  • Зазвичай зовнішні помилки (файли, мережа)
  • Приклади: IOException, FileNotFoundException
// Неперевірені - не потрібно перехоплювати
int result = 10 / 0;  // ArithmeticException

// Перевірені - потрібно перехоплювати або оголошувати
try {
    FileReader file = new FileReader("data.txt");  // IOException
} catch (IOException e) {
    // Обробка помилки
}

Практичні вправи

  1. Калькулятор безпечного ділення: Створіть метод, який ділить два числа. Обробіть ділення на нуль і недійсні вхідні дані.

  2. Налаштування рівня гравця: Створіть метод, який встановлює рівень гравця (1-100). Генеруйте виняток, якщо вхідні дані недійсні, і обробляйте його в main.

  3. Безпечний доступ до масиву: Створіть метод, який безпечно отримує доступ до елементів масиву. Повертайте null, якщо індекс недійсний, замість того, щоб викликати збій.

  4. Аналізатор конфігурації: Прочитайте рядок конфігурації, наприклад "maxPlayers=10", і проаналізуйте його. Обробляйте невірні формати коректно.

  5. Перевірка імені файлу: Створіть метод, який перевіряє, чи ім'я файлу є вірним (без спеціальних символів, не порожнє). Видавайте винятки для невірних імен.