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

На что укажет компилятор при наличии проверяемых исключений?

2.0 Middle🔥 121 комментариев
#Docker, Kubernetes и DevOps#JVM и управление памятью#ORM и Hibernate

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

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

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

Проверяемые исключения (Checked Exceptions): Контроль компилятора

Отличный вопрос о Java исключениях и compile-time проверках! Позвольме разобраться в том, как компилятор контролирует проверяемые исключения.

Что такое Checked Exception?

Проверяемое исключение — это исключение, которое ОБЯЗАНО быть обработано или объявлено в сигнатуре метода.

// Иерархия исключений
Throwable
├── Exception (CHECKED)
│   ├── IOException (CHECKED)
│   ├── SQLException (CHECKED)
│   ├── ClassNotFoundException (CHECKED)
│   ├── ... (все остальные Exception)
│   └── RuntimeException (UNCHECKED)
│       ├── NullPointerException
│       ├── IllegalArgumentException
│       ├── IndexOutOfBoundsException
│       └── ... (остальные Runtime)
└── Error (обычно не ловим)
    ├── OutOfMemoryError
    ├── StackOverflowError
    └── VirtualMachineError

Ошибки компилятора при Checked Exceptions

Ошибка 1: Не обработано checked исключение

public void readFile() {
    // ❌ ОШИБКА КОМПИЛЯТОРА!
    // error: unreported exception FileNotFoundException;
    // must be caught or declared to be thrown
    FileInputStream fis = new FileInputStream("file.txt");
}

// Исправление 1: Обработать try-catch
public void readFileFixed1() {
    try {
        FileInputStream fis = new FileInputStream("file.txt");
        // ...
    } catch (FileNotFoundException e) {
        System.out.println("File not found");
    }
}

// Исправление 2: Объявить throws
public void readFileFixed2() throws FileNotFoundException {
    FileInputStream fis = new FileInputStream("file.txt");
}

Ошибка 2: Множественные checked исключения

public void complexOperation() throws IOException, SQLException, ClassNotFoundException {
    // Объявляем ВСЕ checked исключения
    Class.forName("com.mysql.jdbc.Driver");
    Connection conn = DriverManager.getConnection("jdbc:mysql://localhost/db");
    FileWriter fw = new FileWriter("output.txt");
}

// ❌ НЕПРАВИЛЬНО: Забыли один
public void complexOperation() throws IOException, SQLException {
    Class.forName("com.mysql.jdbc.Driver");  // ❌ ClassNotFoundException не объявлен!
}

// ✅ ПРАВИЛЬНО: Объявлены все или обработаны
public void complexOperation() throws IOException, SQLException, ClassNotFoundException {
    Class.forName("com.mysql.jdbc.Driver");
    Connection conn = DriverManager.getConnection("jdbc:mysql://localhost/db");
    FileWriter fw = new FileWriter("output.txt");
}

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

Пример 1: Правильная обработка

public class FileProcessor {
    
    // ✅ ВАРИАНТ 1: Обработка в методе
    public String readFile(String filename) {
        try {
            FileInputStream fis = new FileInputStream(filename);
            byte[] data = new byte[1024];
            int bytesRead = fis.read(data);
            fis.close();
            return new String(data, 0, bytesRead);
        } catch (FileNotFoundException e) {
            System.out.println("File not found: " + filename);
            return null;
        } catch (IOException e) {
            System.out.println("IO error: " + e.getMessage());
            return null;
        }
    }
    
    // ✅ ВАРИАНТ 2: Объявление throws
    public String readFileWithThrows(String filename) throws IOException {
        FileInputStream fis = new FileInputStream(filename);
        byte[] data = new byte[1024];
        int bytesRead = fis.read(data);
        fis.close();
        return new String(data, 0, bytesRead);
    }
    
    // ✅ ВАРИАНТ 3: Обработка с throws
    public void processFile(String filename) throws IOException {
        String content = readFileWithThrows(filename);
        if (content == null) {
            throw new IOException("Failed to read file");
        }
    }
}

// Использование
public class Main {
    public static void main(String[] args) {
        FileProcessor processor = new FileProcessor();
        
        // Вариант 1: Обработана в методе
        String content1 = processor.readFile("file.txt");
        
        // Вариант 2 & 3: Нужно обработать
        try {
            String content2 = processor.readFileWithThrows("file.txt");
        } catch (IOException e) {
            System.out.println("Cannot read file");
        }
    }
}

Обработка с Try-With-Resources

Java 7+ — автоматическое закрытие ресурсов

// ❌ СТАРЫЙ СПОСОБ: Ручное управление
public String readFileOld(String filename) throws IOException {
    FileInputStream fis = null;
    try {
        fis = new FileInputStream(filename);
        byte[] data = new byte[1024];
        fis.read(data);
        return new String(data);
    } finally {
        if (fis != null) {
            fis.close();  // IOException может быть выброшена!
        }
    }
}

// ✅ НОВЫЙ СПОСОБ: Try-with-resources
public String readFileNew(String filename) throws IOException {
    try (FileInputStream fis = new FileInputStream(filename)) {
        byte[] data = new byte[1024];
        fis.read(data);
        return new String(data);
    }
    // fis.close() вызовется автоматически
}

// ✅ МНОЖЕСТВЕННЫЕ РЕСУРСЫ
public void copyFile(String from, String to) throws IOException {
    try (FileInputStream fis = new FileInputStream(from);
         FileOutputStream fos = new FileOutputStream(to)) {
        byte[] buffer = new byte[1024];
        int bytesRead;
        while ((bytesRead = fis.read(buffer)) != -1) {
            fos.write(buffer, 0, bytesRead);
        }
    }
}

Checked vs Unchecked Exceptions

// CHECKED Exception
public class CheckedExceptionExample {
    public void connectToDatabase() throws SQLException {
        // Компилятор ТРЕБУЕТ обработать или объявить
        Connection conn = DriverManager.getConnection("jdbc:mysql://...");
    }
}

// UNCHECKED Exception
public class UncheckedExceptionExample {
    public void divideNumbers(int a, int b) {
        // Компилятор НЕ требует обработку
        int result = a / b;  // ArithmeticException — unchecked
    }
}

// Сравнение

| Характеристика | Checked | Unchecked |
|--------|---------|----------|
| **Наследует** | Exception | RuntimeException |
| **Проверка** | Compile-time | Runtime |
| **Обязательна обработка** | ДА | НЕТ |
| **Должен быть объявлен** | throws | Нет |
| **Примеры** | IOException, SQLException | NPE, IAE, IndexOOB |
| **Когда** | Предсказуемые ошибки | Программные ошибки |

Рекомендации Spring / Modern Java

Современный подход: Избегать checked exceptions

// ❌ СТАРЫЙ JAVA СПОСОБ: Много checked exceptions
public class UserRepository {
    public User findById(Long id) throws SQLException {
        // Много try-catch в коде вызова
        try {
            // database code
        } catch (SQLException e) {
            throw e;
        }
    }
}

// ✅ MODERN APPROACH: Обернуть в unchecked
@Repository
public class UserRepository {
    public User findById(Long id) {
        try {
            // database code
        } catch (SQLException e) {
            throw new DataAccessException("Failed to find user", e);
        }
    }
}

// Spring автоматически переводит SQLException → DataAccessException
// Это unchecked exception, код более чистый

Spring Data JPA — больше нет checked exceptions

// Старый JDBC (checked exceptions)
public User findById(Long id) throws SQLException {
    // Нужно обрабатывать или объявлять
}

// Spring Data JPA (unchecked exceptions)
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    // Компилятор НЕ требует обработку
    // Spring оборачивает SQLException в DataAccessException
}

Best Practices

// ✅ ПРАВИЛО 1: Обрабатывай в том месте, где можешь
public void processFile(String filename) {
    try {
        String content = readFile(filename);
        // Process content
    } catch (IOException e) {
        logger.error("Cannot read file: " + filename, e);
        // Handle error appropriately
    }
}

// ✅ ПРАВИЛО 2: Не игнорируй исключения
// ❌ ПЛОХО
try {
    readFile("file.txt");
} catch (IOException e) {
    // Молча игнорируем!
}

// ✅ ХОРОШО
try {
    readFile("file.txt");
} catch (IOException e) {
    logger.warn("File not found, using default");
    useDefaultFile();
}

// ✅ ПРАВИЛО 3: Оборачивай в нужном контексте
public User getUserData(String filename) throws UserDataException {
    try {
        return parseUserFile(filename);
    } catch (IOException e) {
        // Оборачиваем в domain-specific исключение
        throw new UserDataException("Cannot load user data", e);
    }
}

// ✅ ПРАВИЛО 4: Используй try-with-resources
public void readAndProcess(String filename) throws IOException {
    try (BufferedReader reader = new BufferedReader(
            new FileReader(filename))) {
        String line;
        while ((line = reader.readLine()) != null) {
            processLine(line);
        }
    }  // Автоматическое закрытие reader
}

// ✅ ПРАВИЛО 5: Никогда не catch (Exception e)
// ❌ ПЛОХО
try {
    complexOperation();
} catch (Exception e) {
    // Ловим всё, включая RuntimeException
}

// ✅ ХОРОШО
try {
    complexOperation();
} catch (IOException | SQLException e) {
    logger.error("Database or IO error", e);
}

Практический пример: Service Layer

// DAO слой (работает с JDBC/Spring Data)
@Repository
public class OrderDAO {
    public Order findById(Long id) throws SQLException {
        // Работаем с checked exception
        try (Connection conn = dataSource.getConnection()) {
            // SQL query
        } catch (SQLException e) {
            throw e;  // Или оборачиваем
        }
    }
}

// Service слой (бизнес-логика)
@Service
public class OrderService {
    private final OrderDAO dao;
    
    public OrderDTO getOrder(Long id) {
        try {
            Order order = dao.findById(id);
            return convertToDTO(order);
        } catch (SQLException e) {
            // Логируем и выбрасываем unchecked
            logger.error("Database error", e);
            throw new OrderServiceException("Cannot fetch order", e);
        }
    }
}

// Controller слой
@RestController
public class OrderController {
    private final OrderService service;
    
    @GetMapping("/orders/{id}")
    public ResponseEntity<OrderDTO> getOrder(@PathVariable Long id) {
        // Нет checked exception для обработки
        try {
            OrderDTO order = service.getOrder(id);
            return ResponseEntity.ok(order);
        } catch (OrderServiceException e) {
            return ResponseEntity.notFound().build();
        }
    }
}

Выводы

  1. Checked Exception требуется обработать или объявить throws
  2. Компилятор контролирует на compile-time
  3. Используй try-catch для локальной обработки
  4. Используй throws для передачи ответственности вверх
  5. Современный подход: оборачивать checked в unchecked
  6. Используй try-with-resources для управления ресурсами
  7. Логируй ошибки перед перебросом исключения
  8. Never catch (Exception e) — ловись специфичные типы
На что укажет компилятор при наличии проверяемых исключений? | PrepBro