← Назад к вопросам

Какие знаешь способы обработки непроверяемых исключений?

1.8 Middle🔥 191 комментариев
#Основы Java

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Способы обработки непроверяемых исключений (Unchecked Exceptions)

Непроверяемые исключения (Runtime Exceptions) — это исключения, которые наследуются от RuntimeException и не требуют явной обработки или объявления в сигнатуре метода. Однако правильная их обработка критична для надёжности приложения.

Типы исключений в Java

// Throwable
//  ├── Error (не обрабатываем: OutOfMemoryError, StackOverflowError)
//  └── Exception
//      ├── Checked (CheckedException) - НУЖНО обрабатывать
//      │   ├── IOException
//      │   ├── SQLException
//      │   └── ...
//      └── Unchecked (RuntimeException) - обрабатываем опционально
//          ├── NullPointerException
//          ├── ArrayIndexOutOfBoundsException
//          ├── IllegalArgumentException
//          └── ...

1. Try-Catch блоки

Базовая обработка:

public double divide(double a, double b) {
    try {
        return a / b;  // Может быть Infinity при b=0.0
    } catch (ArithmeticException e) {
        System.out.println("Ошибка деления: " + e.getMessage());
        return 0;  // Значение по умолчанию
    }
}

// Специфичная обработка разных типов
public void process(String data) {
    try {
        int value = Integer.parseInt(data);
        System.out.println("Значение: " + value);
    } catch (NumberFormatException e) {
        System.out.println("Ошибка парсинга числа: " + data);
    } catch (NullPointerException e) {
        System.out.println("Данные null");
    }
}

Multi-catch в Java 7+:

public void readFile(String filename) {
    try {
        File file = new File(filename);
        Scanner scanner = new Scanner(file);
        while (scanner.hasNextLine()) {
            System.out.println(scanner.nextLine());
        }
        scanner.close();
    } catch (FileNotFoundException | IOException e) {
        System.out.println("Ошибка файла: " + e.getMessage());
    }
}

2. Try-Catch-Finally

Finally для очистки ресурсов:

BufferedReader reader = null;
try {
    reader = new BufferedReader(new FileReader("file.txt"));
    String line = reader.readLine();
    System.out.println(line);
} catch (IOException e) {
    System.out.println("Ошибка ввода: " + e.getMessage());
} finally {
    // Выполнится ВСЕГДА, даже при исключении
    if (reader != null) {
        try {
            reader.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

3. Try-with-resources (Java 7+) - ЛУЧШИЙ СПОСОБ

Автоматическое управление ресурсами:

// Лучше всего для управления ресурсами
public void readFile(String filename) throws IOException {
    try (BufferedReader reader = new BufferedReader(new FileReader(filename))) {
        String line;
        while ((line = reader.readLine()) != null) {
            System.out.println(line);
        }
    }  // reader.close() вызовется автоматически
}

// С несколькими ресурсами
public void copyFile(String source, String destination) throws IOException {
    try (BufferedReader reader = new BufferedReader(new FileReader(source));
         BufferedWriter writer = new BufferedWriter(new FileWriter(destination))) {
        
        String line;
        while ((line = reader.readLine()) != null) {
            writer.write(line);
            writer.newLine();
        }
    }  // Оба ресурса закроются автоматически
}

// Работает с любым AutoCloseable
public void withDatabase() {
    try (Connection conn = DriverManager.getConnection(url)) {
        // работа с БД
    } catch (SQLException e) {
        e.printStackTrace();
    }
}

4. Кастомные исключения

Создание своих исключений:

// Unchecked (наследуем RuntimeException)
public class InsufficientFundsException extends RuntimeException {
    private final double required;
    private final double available;
    
    public InsufficientFundsException(String message, double required, double available) {
        super(message);
        this.required = required;
        this.available = available;
    }
    
    public double getRequired() { return required; }
    public double getAvailable() { return available; }
}

// Использование
public void withdraw(double amount) {
    if (amount > balance) {
        throw new InsufficientFundsException(
            "Недостаточно средств",
            amount,
            balance
        );
    }
    balance -= amount;
}

// Обработка
try {
    account.withdraw(1000);
} catch (InsufficientFundsException e) {
    System.out.println("Требуется: " + e.getRequired());
    System.out.println("Доступно: " + e.getAvailable());
}

5. Логирование исключений

SLF4J + Logback:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class UserService {
    private static final Logger logger = LoggerFactory.getLogger(UserService.class);
    
    public User getUserById(Long id) {
        try {
            if (id == null || id < 0) {
                throw new IllegalArgumentException("ID не может быть null или отрицательным");
            }
            return userRepository.findById(id)
                .orElseThrow(() -> new UserNotFoundException("Пользователь не найден: " + id));
        } catch (IllegalArgumentException e) {
            logger.warn("Некорректный ID: {}", id, e);
            throw e;
        } catch (UserNotFoundException e) {
            logger.info("Пользователь не найден: {}", id);
            throw e;
        } catch (Exception e) {
            logger.error("Неожиданная ошибка при получении пользователя", e);
            throw new RuntimeException("Ошибка базы данных", e);
        }
    }
}

6. Обработка на уровне приложения

Global Exception Handler в Spring:

@ControllerAdvice
public class GlobalExceptionHandler {
    
    @ExceptionHandler(NullPointerException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ResponseEntity<ErrorResponse> handleNullPointer(NullPointerException e) {
        return ResponseEntity.badRequest().body(
            new ErrorResponse("Null указатель", e.getMessage())
        );
    }
    
    @ExceptionHandler(IllegalArgumentException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ResponseEntity<ErrorResponse> handleIllegalArgument(IllegalArgumentException e) {
        return ResponseEntity.badRequest().body(
            new ErrorResponse("Некорректный аргумент", e.getMessage())
        );
    }
    
    @ExceptionHandler(Exception.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public ResponseEntity<ErrorResponse> handleGeneric(Exception e) {
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(
            new ErrorResponse("Внутренняя ошибка сервера", e.getMessage())
        );
    }
}

public record ErrorResponse(String error, String message) {}

7. Optional вместо null проверок

Исключение: NullPointerException

// ПЛОХО: может быть NPE
public String getUserName(User user) {
    return user.getName();  // NPE если user null
}

// ХОРОШО: Optional
public Optional<String> getUserName(User user) {
    return Optional.ofNullable(user)
        .map(User::getName);
}

// Использование
String name = getUserName(user)
    .orElse("Unknown");

// Или с обработкой ошибки
getUserName(user)
    .ifPresentOrElse(
        name -> System.out.println("Имя: " + name),
        () -> System.out.println("Пользователь не найден")
    );

8. Функциональный подход - Result типы

Использование Result wrappers:

public class Result<T> {
    private final T value;
    private final Exception exception;
    private final boolean success;
    
    private Result(T value, Exception exception) {
        this.success = exception == null;
        this.value = value;
        this.exception = exception;
    }
    
    public static <T> Result<T> success(T value) {
        return new Result<>(value, null);
    }
    
    public static <T> Result<T> failure(Exception e) {
        return new Result<>(null, e);
    }
    
    public boolean isSuccess() { return success; }
    public T getValue() { return value; }
    public Exception getException() { return exception; }
    
    public <U> Result<U> map(Function<T, U> fn) {
        if (!success) return failure(exception);
        try {
            return success(fn.apply(value));
        } catch (Exception e) {
            return failure(e);
        }
    }
}

// Использование
Result<User> result = getUserFromDb(id)
    .map(User::getName)
    .map(String::toUpperCase);

if (result.isSuccess()) {
    System.out.println(result.getValue());
} else {
    System.out.println("Ошибка: " + result.getException().getMessage());
}

9. Defensive Programming

Проверка аргументов в начале метода:

public class BankAccount {
    private final String accountNumber;
    private double balance;
    
    public BankAccount(String accountNumber, double initialBalance) {
        // Явная проверка и выброс исключения
        if (accountNumber == null || accountNumber.trim().isEmpty()) {
            throw new IllegalArgumentException("Номер счёта не может быть пустым");
        }
        if (initialBalance < 0) {
            throw new IllegalArgumentException("Начальный баланс не может быть отрицательным");
        }
        
        this.accountNumber = accountNumber;
        this.balance = initialBalance;
    }
    
    public void deposit(double amount) {
        if (amount <= 0) {
            throw new IllegalArgumentException("Сумма должна быть положительной");
        }
        balance += amount;
    }
    
    public void withdraw(double amount) {
        if (amount <= 0) {
            throw new IllegalArgumentException("Сумма должна быть положительной");
        }
        if (amount > balance) {
            throw new InsufficientFundsException("Недостаточно средств");
        }
        balance -= amount;
    }
}

10. Пример в реальной системе

@Service
public class OrderService {
    private final OrderRepository orderRepository;
    private final PaymentService paymentService;
    private final Logger logger = LoggerFactory.getLogger(OrderService.class);
    
    @Transactional
    public Order createOrder(OrderRequest request) {
        try {
            // Валидация
            if (request == null || request.getItems().isEmpty()) {
                throw new IllegalArgumentException("Заказ не может быть пустым");
            }
            
            // Бизнес логика
            Order order = new Order();
            order.setItems(request.getItems());
            order.setStatus("PENDING");
            
            // Сохранение
            Order savedOrder = orderRepository.save(order);
            
            // Оплата
            try {
                paymentService.processPayment(savedOrder);
                savedOrder.setStatus("PAID");
            } catch (PaymentException e) {
                logger.error("Ошибка платежа для заказа {}", savedOrder.getId(), e);
                savedOrder.setStatus("PAYMENT_FAILED");
                throw e;
            }
            
            return orderRepository.save(savedOrder);
            
        } catch (IllegalArgumentException e) {
            logger.warn("Ошибка валидации: {}", e.getMessage());
            throw e;
        } catch (Exception e) {
            logger.error("Неожиданная ошибка при создании заказа", e);
            throw new RuntimeException("Ошибка создания заказа", e);
        }
    }
}

Лучшие практики

  • Используй Try-with-resources: для автоматического управления ресурсами
  • Специфичная обработка: лови конкретные исключения, не Exception
  • Логирование: всегда логируй при перехвате
  • Не подавляй исключения: catch (Exception e) {} - плохо
  • Бросай ранние: проверяй аргументы в начале метода
  • Используй Optional: вместо null проверок
  • Собственные исключения: для бизнес логики
  • Global handlers: в Spring приложениях
  • Документируй: какие исключения может выбросить метод

Правильная обработка исключений — это не просто избежание crashes, это часть контракта и гарантии надёжности вашего кода.

Какие знаешь способы обработки непроверяемых исключений? | PrepBro