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 - Після блоку
catchпрограма продовжує виконуватися нормально - Якщо виняток не виникає, блок
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 для завдань очищення, які повинні виконуватися завжди:
- Закриття файлів
- Звільнення ресурсів
- Збереження даних
- Ведення журналу
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-100). Генеруйте виняток, якщо вхідні дані недійсні, і обробляйте його в main.
-
Безпечний доступ до масиву: Створіть метод, який безпечно отримує доступ до елементів масиву. Повертайте null, якщо індекс недійсний, замість того, щоб викликати збій.
-
Аналізатор конфігурації: Прочитайте рядок конфігурації, наприклад "maxPlayers=10", і проаналізуйте його. Обробляйте невірні формати коректно.
-
Перевірка імені файлу: Створіть метод, який перевіряє, чи ім'я файлу є вірним (без спеціальних символів, не порожнє). Видавайте винятки для невірних імен.