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

Почему нельзя выбрасывать RuntimeException?

2.2 Middle🔥 181 комментариев
#Другое

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

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

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

Почему нельзя выбрасывать RuntimeException?

На самом деле, выбрасывать RuntimeException МОЖНО, но это не рекомендуется как лучшая практика. Вопрос больше о том, КОГДА и КАК правильно использовать RuntimeException и когда его выбрасывать, а когда создать свой специализированный исключение.

Различие между Checked и Unchecked исключениями

Java разделяет исключения на две категории:

// 1. Checked Exception (наследуют Exception, но не RuntimeException)
// Обязательно обрабатывать или декларировать в throws
public class FileReader {
    public String readFile(String filename) throws IOException {
        // IOException — checked exception
        // Вызывающий код ОБЯЗАН обработать это исключение
        FileInputStream fis = new FileInputStream(filename);
        return new String(fis.readAllBytes());
    }
}

// 2. Unchecked Exception (наследуют RuntimeException)
// Не требуется обрабатывать (но можно)
public class Calculator {
    public int divide(int a, int b) {
        // ArithmeticException — unchecked exception
        // Обработка необязательна
        return a / b;  // Может выбросить ArithmeticException
    }
}

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

Throwable
├── Exception (Checked - обязательно обрабатывать)
│   ├── IOException
│   ├── SQLException
│   ├── FileNotFoundException
│   └── ... (ваши custom checked exceptions)
│
├── RuntimeException (Unchecked - необязательно обрабатывать)
│   ├── NullPointerException
│   ├── IllegalArgumentException
│   ├── IndexOutOfBoundsException
│   ├── ArithmeticException
│   └── ... (ваши custom unchecked exceptions)
│
└── Error (системные ошибки, не ловим)
    ├── OutOfMemoryError
    ├── StackOverflowError
    └── ...

Почему говорят "нельзя выбрасывать RuntimeException"?

На самом деле это утверждение неточное. Правильнее сказать:

"Не выбрасывай непосредственно базовый RuntimeException класс. Вместо этого создавай специализированные unchecked исключения или используй уже существующие."

Антипаттерн 1: Выбрасывание базового RuntimeException

// ПЛОХО: выбрасываем базовый RuntimeException
public void processUser(User user) {
    if (user == null) {
        throw new RuntimeException("User cannot be null");
    }
    
    if (user.getName().isEmpty()) {
        throw new RuntimeException("User name cannot be empty");
    }
    
    if (user.getAge() < 0) {
        throw new RuntimeException("User age cannot be negative");
    }
}

// Проблемы:
// 1. Невозможно поймать специфичное исключение
// 2. Сложно определить причину ошибки
// 3. Нарушает контракт методов

Паттерн 1: Использование специализированных unchecked исключений

// ХОРОШО: создаём специализированные исключения
public class UserValidationException extends RuntimeException {
    public UserValidationException(String message) {
        super(message);
    }
    
    public UserValidationException(String message, Throwable cause) {
        super(message, cause);
    }
}

public class InvalidAgeException extends RuntimeException {
    public InvalidAgeException(String message) {
        super(message);
    }
}

// Используем специализированные исключения
public void processUser(User user) {
    if (user == null) {
        throw new UserValidationException("User cannot be null");
    }
    
    if (user.getName().isEmpty()) {
        throw new UserValidationException("User name cannot be empty");
    }
    
    if (user.getAge() < 0) {
        throw new InvalidAgeException("User age cannot be negative");
    }
}

// Теперь можно ловить специфичные ошибки
try {
    processUser(user);
} catch (InvalidAgeException e) {
    // Обработка ошибки возраста
    System.out.println("Age validation failed: " + e.getMessage());
} catch (UserValidationException e) {
    // Обработка других ошибок валидации
    System.out.println("User validation failed: " + e.getMessage());
}

Когда использовать RuntimeException (unchecked)

1. Ошибки программирования (programming errors)

// NullPointerException — ошибка программиста
public class UserService {
    public void saveUser(User user) {
        if (user == null) {
            throw new IllegalArgumentException("User cannot be null");
        }
        // save user
    }
}

// IllegalArgumentException — неверный аргумент
public void setAge(int age) {
    if (age < 0 || age > 150) {
        throw new IllegalArgumentException("Age must be between 0 and 150");
    }
    this.age = age;
}

2. Состояния, которые должны никогда не произойти

// IllegalStateException — неверное состояние объекта
public class ConnectionPool {
    private boolean closed;
    
    public Connection getConnection() {
        if (closed) {
            throw new IllegalStateException("Connection pool is closed");
        }
        return getNextConnection();
    }
}

// UnsupportedOperationException — операция не поддерживается
public class ReadOnlyList<E> extends AbstractList<E> {
    @Override
    public E set(int index, E element) {
        throw new UnsupportedOperationException("List is read-only");
    }
}

3. Ошибки конфигурации

// Ошибка при инициализации (RuntimeException)
public class DatabaseConfiguration {
    private String jdbcUrl;
    
    public DatabaseConfiguration(Properties props) {
        this.jdbcUrl = props.getProperty("db.url");
        
        if (jdbcUrl == null || jdbcUrl.isEmpty()) {
            throw new RuntimeException("Missing required property: db.url");
        }
    }
}

// Почему RuntimeException?
// Потому что это ошибка конфигурации (deployment issue),
// её невозможно обработать во время выполнения.
// Приложение не может продолжать работу без этого конфига.

Когда использовать Checked Exception

1. Операции, которые могут не удаться в runtime

// Checked exception — ошибка операции, которую нужно обработать
public class FileService {
    public String readFile(String filename) throws IOException {
        // IOException — обязательно обработать или переброс
        // Потому что файл может не существовать, нет прав доступа и т.д.
        return Files.readString(Paths.get(filename));
    }
}

// Вызывающий код ОБЯЗАН обработать
try {
    String content = fileService.readFile("data.txt");
} catch (IOException e) {
    System.out.println("Failed to read file: " + e.getMessage());
}

2. Внешние операции (БД, сеть, файловая система)

// Checked exception для операций БД
public class UserRepository {
    public User findById(Long id) throws SQLException {
        // SQLException — проверяется компилятором
        // Разработчик должен явно обработать
        ResultSet rs = connection.executeQuery("SELECT * FROM users WHERE id = ?");
        return mapToUser(rs);
    }
}

// Checked exception для сетевых операций
public class HttpClient {
    public String get(String url) throws IOException, HttpException {
        // IOException — сеть может быть недоступна
        // HttpException — сервер может вернуть ошибку
        return makeRequest(url);
    }
}

Практический пример: Правильная иерархия исключений

// 1. Базовое unchecked исключение для бизнес-логики
public class BusinessException extends RuntimeException {
    public BusinessException(String message) {
        super(message);
    }
    
    public BusinessException(String message, Throwable cause) {
        super(message, cause);
    }
}

// 2. Специализированные unchecked исключения
public class InsufficientFundsException extends BusinessException {
    private BigDecimal requiredAmount;
    private BigDecimal availableAmount;
    
    public InsufficientFundsException(
        String message,
        BigDecimal required,
        BigDecimal available
    ) {
        super(message);
        this.requiredAmount = required;
        this.availableAmount = available;
    }
    
    public BigDecimal getRequiredAmount() {
        return requiredAmount;
    }
    
    public BigDecimal getAvailableAmount() {
        return availableAmount;
    }
}

public class UserNotFoundException extends BusinessException {
    public UserNotFoundException(Long userId) {
        super("User not found: " + userId);
    }
}

// 3. Использование в бизнес-логике
public class BankService {
    public void transfer(String fromAccount, String toAccount, BigDecimal amount) {
        Account from = accountRepository.findByNumber(fromAccount);
        if (from == null) {
            throw new UserNotFoundException(fromAccount);
        }
        
        if (from.getBalance().compareTo(amount) < 0) {
            throw new InsufficientFundsException(
                "Insufficient funds for transfer",
                amount,
                from.getBalance()
            );
        }
        
        from.withdraw(amount);
        Account to = accountRepository.findByNumber(toAccount);
        to.deposit(amount);
    }
}

// 4. Обработка в контроллере
@RestController
public class TransferController {
    @PostMapping("/transfer")
    public ResponseEntity<?> transfer(@RequestBody TransferRequest request) {
        try {
            bankService.transfer(
                request.getFromAccount(),
                request.getToAccount(),
                request.getAmount()
            );
            return ResponseEntity.ok().build();
        } catch (UserNotFoundException e) {
            return ResponseEntity.notFound().build();
        } catch (InsufficientFundsException e) {
            return ResponseEntity
                .status(HttpStatus.BAD_REQUEST)
                .body(new ErrorResponse(
                    "Insufficient funds",
                    e.getRequiredAmount(),
                    e.getAvailableAmount()
                ));
        } catch (BusinessException e) {
            return ResponseEntity
                .status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(new ErrorResponse(e.getMessage()));
        }
    }
}

Сравнение Checked vs Unchecked

АспектChecked ExceptionUnchecked (RuntimeException)
НаследуетExceptionRuntimeException
Обязательно обрабатыватьДаНет
Когда использоватьВосстанавливаемые ошибкиОшибки программирования
ПримерыIOException, SQLExceptionNullPointerException, IllegalArgumentException
Переброс в throwsОбязательноОпционально

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

// ОШИБКА 1: Выбрасываем базовый RuntimeException
public void validateUser(User user) {
    if (user.getAge() < 0) {
        throw new RuntimeException("Invalid age"); // ПЛОХО
    }
}

// ИСПРАВЛЕНИЕ 1: Используем специализированный unchecked exception
public void validateUser(User user) {
    if (user.getAge() < 0) {
        throw new IllegalArgumentException("User age cannot be negative"); // ХОРОШО
    }
}

// ОШИБКА 2: Ловим базовый RuntimeException
try {
    processUser(user);
} catch (RuntimeException e) {  // ПЛОХО: слишком широко
    System.out.println("Error: " + e.getMessage());
}

// ИСПРАВЛЕНИЕ 2: Ловим специфичное исключение
try {
    processUser(user);
} catch (InvalidAgeException e) {  // ХОРОШО: специфично
    System.out.println("Age validation failed: " + e.getMessage());
}

// ОШИБКА 3: Checked exception для невосстанавливаемой ошибки
public void deleteFile(String filename) throws IOException {
    if (!Files.exists(Paths.get(filename))) {
        throw new IOException("File not found"); // ПЛОХО
    }
}

// ИСПРАВЛЕНИЕ 3: Unchecked exception для невосстанавливаемой ошибки
public void deleteFile(String filename) {
    if (!Files.exists(Paths.get(filename))) {
        throw new IllegalArgumentException("File not found"); // ХОРОШО
    }
}

Итоговый чеклист

✓ Не выбрасывай непосредственно базовый RuntimeException ✓ Создавай специализированные unchecked исключения для бизнес-логики ✓ Используй IllegalArgumentException для неверных аргументов ✓ Используй IllegalStateException для неверного состояния ✓ Используй Checked Exception для восстанавливаемых ошибок (I/O, БД) ✓ Используй Unchecked Exception для ошибок программирования ✓ Всегда включай контекст в сообщение об исключении ✓ Ловй специфичные исключения, а не базовые Exception/RuntimeException

Основной вывод: Проблема не в том, чтобы "не выбрасывать RuntimeException", а в том, чтобы правильно использовать специализированные исключения для каждого сценария ошибок.