Skip to content
IRC-CodingIRC-Coding
Exception Handling JavaTry Catch FinallyThrow ThrowsCustom ExceptionsОбработка исключенийЯзык программирования

Exception Handling в Java: Try-Catch-Finally и Custom Exceptions

Обработка исключений в Java: try-catch-finally, throw, throws. Иерархия Exception, лучшие практики и надёжный код.

S

schutzgeist

14 min read
Exception Handling в Java: Try-Catch-Finally и Custom Exceptions

Обработка исключений в Java: Try-Catch-Finally, Throw, Throws и пользовательские исключения

Это полное руководство по обработке исключений в Java, включающее try-catch-finally, throw, throws и пользовательские исключения с практическими примерами.

Вкратце

Обработка исключений позволяет контролировать обработку ошибок в Java. try-catch-finally перехватывают исключения, throw/throws объявляют их, а пользовательские исключения обеспечивают специфичную обработку ошибок.

Определение

Обработка исключений — это механизм для работы с ошибками и нештатными ситуациями при выполнении программы. Java использует структурированную иерархию исключений.

Иерархия исключений:

  • Throwable: базовый класс для всех ошибок и исключений
  • Error: серьёзные системные ошибки (не обрабатываются)
  • Exception: обрабатываемые исключения
    • Checked Exceptions: проверяются на этапе компиляции
    • Unchecked Exceptions: ошибки выполнения (RuntimeException)

Ключевые концепции:

Try-Catch-Finally

  • try: блок с потенциально ошибочным кодом
  • catch: блок для обработки конкретных исключений
  • finally: блок, выполняемый всегда (даже при исключении)
  • try-with-resources: автоматическое управление ресурсами

Throw и Throws

  • throw: явное выброс исключения
  • throws: объявление исключений в сигнатуре метода
  • rethrow: передача исключения дальше

Пользовательские исключения

  • Собственные классы исключений: специфичная обработка ошибок
  • Business Exceptions: ошибки предметной области
  • Validation Exceptions: ошибки валидации входных данных

Ключевые моменты

  • Обработка исключений: структурированная работа с ошибками в Java
  • Try-Catch-Finally: основные блоки обработки
  • Checked vs Unchecked Exceptions: ошибки компиляции и выполнения
  • Throw vs Throws: выброс и объявление исключений
  • Пользовательские исключения: создание собственных классов
  • Иерархия исключений: Throwable → Error/Exception → RuntimeException
  • Важно для надёжного и отказоустойчивого ПО

Основные компоненты

  1. Иерархия исключений: Throwable, Error, Exception, RuntimeException
  2. Try-Catch-Finally: структура обработки ошибок
  3. Throw/Throws: выброс и объявление исключений
  4. Пользовательские исключения: специальные классы ошибок
  5. Try-with-Resources: автоматическое управление ресурсами
  6. Exception Chaining: связывание исключений
  7. Best Practices: рекомендации по обработке исключений
  8. Logging: журналирование ошибок

Практические примеры

1. Базовая обработка исключений

import java.io.*;
import java.util.*;

public class ExceptionGrundlagen {
    
    public static void main(String[] args) {
        System.out.println("=== Exception Handling Grundlagen ===");
        
        // Einfache try-catch
        einfachesTryCatch();
        
        // Mehrere catch-Blöcke
        mehrfachCatch();
        
        // Finally-Block
        finallyDemo();
        
        // Try-with-Resources
        tryWithResourcesDemo();
        
        // Exception-Auslösung
        exceptionAusloesen();
    }
    
    private static void einfachesTryCatch() {
        System.out.println("\n--- Einfaches Try-Catch ---");
        
        try {
            // Potentiell fehlerhafter Code
            int ergebnis = 10 / 0;  // ArithmeticException
            System.out.println("Ergebnis: " + ergebnis);
            
        } catch (ArithmeticException e) {
            System.out.println("Fehler: Division durch Null");
            System.out.println("Exception-Typ: " + e.getClass().getSimpleName());
            System.out.println("Nachricht: " + e.getMessage());
        }
        
        System.out.println("Programm läuft weiter");
    }
    
    private static void mehrfachCatch() {
        System.out.println("\n--- Mehrfache Catch-Blöcke ---");
        
        String[] zahlen = {"10", "5", "abc", "2"};
        
        for (String zahlString : zahlen) {
            try {
                int zahl = Integer.parseInt(zahlString);
                int ergebnis = 100 / zahl;
                System.out.println("100 / " + zahl + " = " + ergebnis);
                
            } catch (NumberFormatException e) {
                System.out.println("Fehler: '" + zahlString + "' ist keine Zahl");
                
            } catch (ArithmeticException e) {
                System.out.println("Fehler: Division durch Null bei " + zahlString);
                
            } catch (Exception e) {
                // Allgemeiner Exception-Handler
                System.out.println("Unerwarteter Fehler: " + e.getMessage());
            }
        }
    }
    
    private static void finallyDemo() {
        System.out.println("\n--- Finally-Block Demo ---");
        
        BufferedReader reader = null;
        
        try {
            // Ressource öffnen
            reader = new BufferedReader(new FileReader("nichtexistente.txt"));
            String zeile = reader.readLine();
            System.out.println("Gelesen: " + zeile);
            
        } catch (FileNotFoundException e) {
            System.out.println("Datei nicht gefunden: " + e.getMessage());
            
        } catch (IOException e) {
            System.out.println("Lesefehler: " + e.getMessage());
            
        } finally {
            // Wird immer ausgeführt
            System.out.println("Finally-Block wird ausgeführt");
            
            if (reader != null) {
                try {
                    reader.close();
                    System.out.println("Reader geschlossen");
                } catch (IOException e) {
                    System.out.println("Fehler beim Schließen: " + e.getMessage());
                }
            }
        }
    }
    
    private static void tryWithResourcesDemo() {
        System.out.println("\n--- Try-with-Resources Demo ---");
        
        // Automatische Ressourcenverwaltung
        try (BufferedReader reader = new BufferedReader(new FileReader("beispiel.txt"))) {
            
            String zeile;
            int zeilenNummer = 1;
            
            while ((zeile = reader.readLine()) != null) {
                System.out.println("Zeile " + zeilenNummer + ": " + zeile);
                zeilenNummer++;
                
                if (zeilenNummer > 3) {
                    throw new IOException("Künstlicher Fehler nach 3 Zeilen");
                }
            }
            
        } catch (FileNotFoundException e) {
            System.out.println("Datei nicht gefunden: " + e.getMessage());
            
        } catch (IOException e) {
            System.out.println("IO-Fehler: " + e.getMessage());
            
        } finally {
            System.out.println("Try-with-Resources beendet (Reader automatisch geschlossen)");
        }
    }
    
    private static void exceptionAusloesen() {
        System.out.println("\n--- Exception-Auslösung ---");
        
        try {
            validiereAlter(15);  // Löst Exception aus
            
        } catch (IllegalArgumentException e) {
            System.out.println("Validierungsfehler: " + e.getMessage());
        }
        
        try {
            validiereAlter(25);  // Kein Fehler
            
        } catch (IllegalArgumentException e) {
            System.out.println("Dies sollte nicht passieren: " + e.getMessage());
        }
        
        System.out.println("Alter-Validierung abgeschlossen");
    }
    
    private static void validiereAlter(int alter) {
        if (alter < 18) {
            throw new IllegalArgumentException("Alter muss mindestens 18 sein, aber war: " + alter);
        }
        
        System.out.println("Alter " + alter + " ist gültig");
    }
}

2. Пользовательские исключения и клауза throws

// Классы пользовательских исключений
class GeschaeftsException extends Exception {
    public GeschaeftsException(String nachricht) {
        super(nachricht);
    }
    
    public GeschaeftsException(String nachricht, Throwable ursache) {
        super(nachricht, ursache);
    }
}

class ValidierungsException extends RuntimeException {
    private String feld;
    
    public ValidierungsException(String feld, String nachricht) {
        super(nachricht);
        this.feld = feld;
    }
    
    public String getFeld() {
        return feld;
    }
}

class DatenbankException extends Exception {
    private int fehlercode;
    
    public DatenbankException(String nachricht, int fehlercode) {
        super(nachricht);
        this.fehlercode = fehlercode;
    }
    
    public int getFehlercode() {
        return fehlercode;
    }
}

// Классы сервисов с обработкой исключений
class KundenService {
    
    // Метод с клаузой throws
    public Kunde findeKunde(int kundenId) throws GeschaeftsException, DatenbankException {
        try {
            // Имитируем доступ к базе данных
            if (kundenId < 1000) {
                throw new DatenbankException("Ungültige Kunden-ID: " + kundenId, 404);
            }
            
            if (kundenId == 1234) {
                throw new DatenbankException("Datenbankverbindung fehlgeschlagen", 500);
            }
            
            // Имитируем успешный доступ
            return new Kunde(kundenId, "Max Mustermann", "max@example.com");
            
        } catch (SQLException e) {
            // Цепочка исключений, сохраняем исходное исключение
            throw new DatenbankException("Datenbankfehler bei Kundensuche", 503);
        }
    }
    
    // Метод с пользовательским исключением
    public void kundeAnlegen(Kunde kunde) throws GeschaeftsException {
        try {
            validiereKunde(kunde);
            
            // Имитируем бизнес-логику
            if (kunde.getName().toLowerCase().contains("test")) {
                throw new GeschaeftsException("Test-Kunden dürfen nicht angelegt werden");
            }
            
            // Клиент будет сохранён в базу данных
            System.out.println("Kunde angelegt: " + kunde.getName());
            
        } catch (ValidierungsException e) {
            // Пробрасываем исключение с дополнительной информацией
            throw new GeschaeftsException("Kundendaten ungültig: " + e.getMessage(), e);
        }
    }
    
    private void validiereKunde(Kunde kunde) {
        if (kunde.getName() == null || kunde.getName().trim().isEmpty()) {
            throw new ValidierungsException("name", "Name darf nicht leer sein");
        }
        
        if (kunde.getEmail() == null || !kunde.getEmail().contains("@")) {
            throw new ValidierungsException("email", "Ungültige E-Mail-Adresse");
        }
        
        if (kunde.getName().length() > 100) {
            throw new ValidierungsException("name", "Name zu lang (max 100 Zeichen)");
        }
    }
}

// Модель данных
class Kunde {
    private int id;
    private String name;
    private String email;
    
    public Kunde(int id, String name, String email) {
        this.id = id;
        this.name = name;
        this.email = email;
    }
    
    // Геттеры
    public int getId() { return id; }
    public String getName() { return name; }
    public String getEmail() { return email; }
}

// Главный класс для демонстрации
public class CustomExceptionDemo {
    
    public static void main(String[] args) {
        System.out.println("=== Custom Exceptions Demo ===");
        
        KundenService service = new KundenService();
        
        // Демо 1: успешный поиск клиента
        try {
            Kunde kunde = service.findeKunde(1001);
            System.out.println("Kunde gefunden: " + kunde.getName());
            
        } catch (GeschaeftsException | DatenbankException e) {
            System.out.println("Fehler bei Kundensuche: " + e.getMessage());
        }
        
        // Демо 2: ошибка базы данных
        try {
            service.findeKunde(999);
            
        } catch (GeschaeftsException e) {
            System.out.println("Geschäftsfehler: " + e.getMessage());
            
        } catch (DatenbankException e) {
            System.out.println("Datenbankfehler (Code " + e.getFehlercode() + "): " + e.getMessage());
        }
        
        // Демо 3: ошибка валидации при создании
        try {
            Kunde ungueltigerKunde = new Kunde(0, "", "ungueltig@");
            service.kundeAnlegen(ungueltigerKunde);
            
        } catch (GeschaeftsException e) {
            System.out.println("Fehler beim Kundenanlegen: " + e.getMessage());
            
            // Выводим исходное исключение
            if (e.getCause() instanceof ValidierungsException) {
                ValidierungsException ve = (ValidierungsException) e.getCause();
                System.out.println("Validierungsfehler in Feld '" + ve.getFeld() + "'");
            }
        }
        
        // Демо 4: нарушение бизнес-правила
        try {
            Kunde testKunde = new Kunde(0, "Test User", "test@example.com");
            service.kundeAnlegen(testKunde);
            
        } catch (GeschaeftsException e) {
            System.out.println("Business Rule Fehler: " + e.getMessage());
        }
        
        // Демо 5: логирование исключений
        exceptionLoggingDemo();
    }
    
    private static void exceptionLoggingDemo() {
        System.out.println("\n--- Exception Logging Demo ---");
        
        try {
            // Имитируем сложную операцию
            komplexeOperation();
            
        } catch (Exception e) {
            // Структурированное логирование
            logException(e);
        }
    }
    
    private static void komplexeOperation() throws Exception {
        try {
            // Первая операция
            ersteOperation();
            
            // Вторая операция
            zweiteOperation();
            
        } catch (IllegalArgumentException e) {
            // Пробрасываем исключение с контекстной информацией
            throw new Exception("Fehler in komplexer Operation", e);
        }
    }
    
    private static void ersteOperation() {
        if (Math.random() > 0.5) {
            throw new IllegalArgumentException("Fehler in erster Operation");
        }
        System.out.println("Erste Operation erfolgreich");
    }
    
    private static void zweiteOperation() {
        if (Math.random() > 0.7) {
            throw new IllegalArgumentException("Fehler in zweiter Operation");
        }
        System.out.println("Zweite Operation erfolgreich");
    }
    
    private static void logException(Exception e) {
        System.out.println("=== Exception Log ===");
        System.out.println("Typ: " + e.getClass().getSimpleName());
        System.out.println("Nachricht: " + e.getMessage());
        System.out.println("Stack Trace:");
        
        // Выводим стек вызовов
        for (StackTraceElement element : e.getStackTrace()) {
            System.out.println("  at " + element.getClassName() + 
                             "." + element.getMethodName() + 
                             "(" + element.getFileName() + 
                             ":" + element.getLineNumber() + ")");
        }
        
        // Логируем исходное исключение
        if (e.getCause() != null) {
            System.out.println("Ursache: " + e.getCause().getClass().getSimpleName() + 
                             " - " + e.getCause().getMessage());
        }
    }
}

3. Продвинутые паттерны обработки исключений

import java.util.*;
import java.util.function.*;

public class AdvancedExceptionPatterns {
    
    // Result Pattern для Fehlerbehandlung без исключений
    static class Result<T> {
        private final T value;
        private final Exception error;
        private final boolean success;
        
        private Result(T value, Exception error, boolean success) {
            this.value = value;
            this.error = error;
            this.success = success;
        }
        
        public static <T> Result<T> success(T value) {
            return new Result<>(value, null, true);
        }
        
        public static <T> Result<T> failure(Exception error) {
            return new Result<>(null, error, false);
        }
        
        public boolean isSuccess() { return success; }
        public boolean isFailure() { return !success; }
        
        public T getValue() {
            if (!success) {
                throw new IllegalStateException("Нет значения при ошибке");
            }
            return value;
        }
        
        public Exception getError() {
            if (success) {
                throw new IllegalStateException("Нет ошибки при успехе");
            }
            return error;
        }
        
        public <U> Result<U> map(Function<T, U> mapper) {
            if (success) {
                try {
                    return Result.success(mapper.apply(value));
                } catch (Exception e) {
                    return Result.failure(e);
                }
            } else {
                return Result.failure(error);
            }
        }
        
        public <U> Result<U> flatMap(Function<T, Result<U>> mapper) {
            if (success) {
                try {
                    return mapper.apply(value);
                } catch (Exception e) {
                    return Result.failure(e);
                }
            } else {
                return Result.failure(error);
            }
        }
        
        public T orElse(T defaultValue) {
            return success ? value : defaultValue;
        }
    }
    
    // Обёртки исключений для функциональных интерфейсов
    @FunctionalInterface
    interface CheckedSupplier<T> {
        T get() throws Exception;
    }
    
    @FunctionalInterface
    interface CheckedRunnable {
        void run() throws Exception;
    }
    
    @FunctionalInterface
    interface CheckedFunction<T, R> {
        R apply(T t) throws Exception;
    }
    
    // Утилиты для обработки исключений
    static class ExceptionUtils {
        
        public static <T> Optional<T> optionalOf(CheckedSupplier<T> supplier) {
            try {
                return Optional.ofNullable(supplier.get());
            } catch (Exception e) {
                return Optional.empty();
            }
        }
        
        public static <T> Result<T> resultOf(CheckedSupplier<T> supplier) {
            try {
                return Result.success(supplier.get());
            } catch (Exception e) {
                return Result.failure(e);
            }
        }
        
        public static void unchecked(CheckedRunnable runnable) {
            try {
                runnable.run();
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
        
        public static <T, R> Function<T, R> uncheckedFunction(CheckedFunction<T, R> function) {
            return t -> {
                try {
                    return function.apply(t);
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            };
        }
        
        public static <T> Supplier<T> uncheckedSupplier(CheckedSupplier<T> supplier) {
            return () -> {
                try {
                    return supplier.get();
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            };
        }
    }
    
    // Механизм повторных попыток
    static class RetryUtils {
        
        public static <T> T retry(int maxAttempts, long delayMs, CheckedSupplier<T> supplier) 
                throws Exception {
            
            Exception lastException = null;
            
            for (int attempt = 1; attempt <= maxAttempts; attempt++) {
                try {
                    return supplier.get();
                    
                } catch (Exception e) {
                    lastException = e;
                    
                    if (attempt == maxAttempts) {
                        break;
                    }
                    
                    System.out.println("Попытка " + attempt + " не удалась, повтор через " + delayMs + "ms");
                    Thread.sleep(delayMs);
                }
            }
            
            throw new Exception("Все " + maxAttempts + " попыток не удались", lastException);
        }
        
        public static <T> Optional<T> retryOptional(int maxAttempts, long delayMs, 
                                                  CheckedSupplier<T> supplier) {
            try {
                return Optional.of(retry(maxAttempts, delayMs, supplier));
            } catch (Exception e) {
                return Optional.empty();
            }
        }
    }
    
    // Сервисные классы с продвинутыми паттернами
    static class DatenbankService {
        
        // С Result Pattern
        public Result<String> leseDatenMitResult(String id) {
            try {
                // Имитация доступа к БД
                if (id == null || id.isEmpty()) {
                    return Result.failure(new IllegalArgumentException("ID не может быть пусто"));
                }
                
                if (id.equals("error")) {
                    return Result.failure(new RuntimeException("Ошибка БД"));
                }
                
                String daten = "Дані для " + id;
                return Result.success(daten);
                
            } catch (Exception e) {
                return Result.failure(e);
            }
        }
        
        // С Optional
        public Optional<String> leseDatenMitOptional(String id) {
            return ExceptionUtils.optionalOf(() -> {
                if (id == null || id.isEmpty()) {
                    throw new IllegalArgumentException("ID не может быть пусто");
                }
                
                if (id.equals("notfound")) {
                    return null;
                }
                
                return "Дані для " + id;
            });
        }
        
        // С повторными попытками
        public String leseDatenMitRetry(String id) throws Exception {
            return RetryUtils.retry(3, 1000, () -> {
                // Имитация неустойчивого соединения
                if (Math.random() > 0.7) {
                    throw new RuntimeException("Ошибка соединения");
                }
                
                return "Стабильные данные для " + id;
            });
        }
    }
    
    public static void main(String[] args) {
        System.out.println("=== Продвинутые паттерны обработки исключений ===");
        
        DatenbankService service = new DatenbankService();
        
        // Демонстрация Result Pattern
        System.out.println("\n--- Демонстрация Result Pattern ---");
        
        Result<String> result1 = service.leseDatenMitResult("123");
        System.out.println("Успех: " + result1.isSuccess());
        result1.ifPresent(value -> System.out.println("Значение: " + value));
        
        Result<String> result2 = service.leseDatenMitResult("error");
        System.out.println("Успех: " + result2.isSuccess());
        result2.ifPresentOrElse(
            value -> System.out.println("Значение: " + value),
            error -> System.out.println("Ошибка: " + error.getMessage())
        );
        
        // Цепочка Result
        Result<Integer> laenge = result1
            .map(String::length)
            .map(laeng -> laeng * 2);
        
        System.out.println("Удвоенная длина: " + laenge.orElse(0));
        
        // Демонстрация Optional Pattern
        System.out.println("\n--- Демонстрация Optional Pattern ---");
        
        Optional<String> opt1 = service.leseDatenMitOptional("123");
        opt1.ifPresent(daten -> System.out.println("Найдено: " + daten));
        
        Optional<String> opt2 = service.leseDatenMitOptional("notfound");
        System.out.println("Найдено: " + opt2.isPresent());
        
        // Optional со значением по умолчанию
        String ergebnis = opt2.orElse("Значение по умолчанию");
        System.out.println("Результат: " + ergebnis);
        
        // Демонстрация повторных попыток
        System.out.println("\n--- Демонстрация повторных попыток ---");
        
        try {
            String daten = service.leseDatenMitRetry("123");
            System.out.println("Успех после повтора: " + daten);
            
        } catch (Exception e) {
            System.out.println("Все повторы не удались: " + e.getMessage());
        }
        
        // Демонстрация Exception Utils
        System.out.println("\n--- Демонстрация Exception Utils ---");
        
        // Unchecked функциональный интерфейс
        List<String> zahlen = Arrays.asList("10", "5", "abc", "2");
        
        zahlen.stream()
            .map(ExceptionUtils.uncheckedFunction(zahl -> Integer.parseInt(zahl) * 2))
            .forEach(ergebnis -> System.out.println("Удвоено: " + ergebnis));
        
        // Логирование исключения с контекстом
        System.out.println("\n--- Исключение с контекстом ---");
        
        try {
            berechneKomplex(10, 0);
            
        } catch (Exception e) {
            logWithContext(e, Map.of(
                "operation", "berechneKomplex",
                "param1", 10,
                "param2", 0,
                "timestamp", System.currentTimeMillis()
            ));
        }
    }
    
    private static int berechneKomplex(int a, int b) throws Exception {
        if (b == 0) {
            throw new ArithmeticException("Деление на ноль");
        }
        
        return a / b;
    }
    
    private static void logWithContext(Exception e, Map<String, Object> context) {
        System.out.println("=== Исключение с контекстом ===");
        System.out.println("Исключение: " + e.getClass().getSimpleName());
        System.out.println("Сообщение: " + e.getMessage());
        System.out.println("Контекст:");
        
        context.forEach((key, value) -> 
            System.out.println("  " + key + ": " + value));
        
        System.out.println("Stack Trace:");
        Arrays.stream(e.getStackTrace())
            .limit(3)
            .forEach(element -> 
                System.out.println("  at " + element.getClassName() + 
                                 "." + element.getMethodName()));
    }
    
    // Вспомогательный метод для Result
    private static <T> void ifPresent(Result<T> result, Consumer<T> action) {
        if (result.isSuccess()) {
            action.accept(result.getValue());
        }
    }
}

Паттерн Result предоставляет функциональный способ работы с ошибками без использования исключений. Вместо выброса исключения метод возвращает объект Result, содержащий либо успешное значение, либо информацию об ошибке. Это позволяет легко комбинировать операции с помощью методов map и flatMap.

CheckedSupplier, CheckedFunction и CheckedRunnable расширяют стандартные функциональные интерфейсы Java, позволяя им выбрасывать проверяемые исключения. Утилиты ExceptionUtils оборачивают эти интерфейсы, преобразуя проверяемые исключения в неправильные для использования в потоках и лямбдах.

Механизм повторных попыток в RetryUtils автоматически повторяет операцию заданное количество раз с интервалом, если выбросается исключение. Это особенно полезно при работе с нестабильными сетевыми соединениями или внешними сервисами.

DatenbankService демонстрирует три различных подхода к обработке ошибок. Метод leseDatenMitResult использует Result Pattern для явной обработки успеха и ошибки. leseDatenMitOptional использует Optional для случаев, где значение может отсутствовать. leseDatenMitRetry комбинирует обработку ошибок с механизмом повторных попыток.

В методе main показано использование всех этих паттернов. Обратите внимание на цепочку Result операций, где мы применяем несколько трансформаций подряд. Также демонстрируется использование CheckedFunction с потоками данных для безопасного преобразования строк в целые числа.

Функция logWithContext показывает, как логировать исключения вместе с контекстной информацией. Это помогает при отладке, так как предоставляет полную информацию об ошибке и состоянии приложения в момент её возникновения.

Иерархия исключений

Throwable
├── Error (Системные ошибки, не обрабатываются)
│   ├── OutOfMemoryError
│   ├── StackOverflowError
│   └── VirtualMachineError
└── Exception (Обрабатываемые исключения)
    ├── Checked Exceptions (Проверка на этапе компиляции)
    │   ├── IOException
    │   │   ├── FileNotFoundException
    │   │   └── SQLException
    │   ├── ClassNotFoundException
    │   └── InterruptedException
    └── RuntimeException (Unchecked Exceptions)
        ├── NullPointerException
        ├── IllegalArgumentException
        ├── ArithmeticException
        ├── IndexOutOfBoundsException
        └── NumberFormatException

Лучшие практики при работе с исключениями

Что нужно делать

  • Ловить конкретные исключения: вместо универсальной Exception
  • Освобождать ресурсы: через finally или try-with-resources
  • Информативные сообщения: понятные описания ошибок
  • Exception Chaining: сохранять исходное исключение
  • Логирование: записывать исключения с контекстом

Что не нужно делать

  • Пустые блоки catch: игнорировать исключения
  • Exception swallowing: подавлять ошибки
  • Over-catch: ловить слишком общие исключения
  • Возвращать null: использовать Optional или Result вместо этого
  • PrintStackTrace: в production коде

Try-with-Resources против Finally

Try-with-Resources (современный подход)

try (BufferedReader reader = new BufferedReader(new FileReader("file.txt"));
     BufferedWriter writer = new BufferedWriter(new FileWriter("output.txt"))) {
    
    // Ресурсы закрываются автоматически
    
} catch (IOException e) {
    // Обработка исключения
}

Блок Finally (традиционный подход)

BufferedReader reader = null;
try {
    reader = new BufferedReader(new FileReader("file.txt"));
    // Работа с reader
} catch (IOException e) {
    // Обработка исключения
} finally {
    if (reader != null) {
        try {
            reader.close();
        } catch (IOException e) {
            // Обработка ошибки при закрытии
        }
    }
}

Паттерны обработки исключений

Template Method Pattern

public abstract class DatabaseTemplate {
    
    public final Result<T> execute(CheckedSupplier<T> operation) {
        try {
            Connection conn = getConnection();
            try {
                return Result.success(operation.get());
            } finally {
                conn.close();
            }
        } catch (Exception e) {
            return Result.failure(e);
        }
    }
    
    protected abstract Connection getConnection() throws SQLException;
}

Decorator Pattern

public class RetryDecorator<T> implements Supplier<T> {
    private final Supplier<T> supplier;
    private final int maxRetries;
    
    public RetryDecorator(Supplier<T> supplier, int maxRetries) {
        this.supplier = supplier;
        this.maxRetries = maxRetries;
    }
    
    @Override
    public T get() {
        Exception lastException = null;
        
        for (int i = 0; i <= maxRetries; i++) {
            try {
                return supplier.get();
            } catch (Exception e) {
                lastException = e;
                if (i < maxRetries) {
                    // Ожидание перед повторной попыткой
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException ie) {
                        Thread.currentThread().interrupt();
                        throw new RuntimeException(ie);
                    }
                }
            }
        }
        
        throw new RuntimeException("All retries failed", lastException);
    }
}

Преимущества и недостатки

Преимущества обработки исключений

  • Контроль ошибок: структурированная обработка сбоев
  • Ясность кода: разделение нормального и аварийного потоков
  • Надёжность: стабильные программы даже при ошибках
  • Отладка: лучший анализ ошибок через stack-trace
  • Поддерживаемость: централизованная обработка ошибок

Недостатки

  • Производительность: обработка исключений имеет накладные расходы
  • Сложность: вложенные конструкции try-catch
  • Overhead: дополнительный код для обработки ошибок
  • Злоупотребление: использование исключений для управления потоком выполнения

Частые вопросы на собеседовании

  1. В чём различие между checked и unchecked исключениями? Checked исключения необходимо объявлять и обрабатывать, unchecked нет (RuntimeException).

  2. Когда выполняется finally? Всегда, независимо от наличия исключения, даже если в try-блоке есть return.

  3. Объясните try-with-resources! Автоматическое освобождение ресурсов для объектов, реализующих AutoCloseable.

  4. Что такое Exception Chaining? Передача исключений с сохранением информации об исходной причине ошибки.

Основные источники

  1. https://docs.oracle.com/javase/tutorial/essential/exceptions/
  2. https://www.baeldung.com/java-exceptions
  3. https://effectivejava.com/
Назад к блогу
Share:

Похожие статьи