← Назад к вопросам
С какими непроверяемыми исключениями сталкивался в работе
2.0 Middle🔥 161 комментариев
#Основы Java
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Непроверяемые исключения в реальной работе
Краткий ответ
Непроверяемые исключения (RuntimeException и его подклассы) - это исключения которые НЕ требуют явной обработки. В реальной работе я сталкивался с множеством непроверяемых исключений: NullPointerException, IllegalArgumentException, ArrayIndexOutOfBoundsException и др.
Основные непроверяемые исключения
// Иерархия непроверяемых исключений
RuntimeException (и все подклассы - непроверяемые)
├── NullPointerException
├── IllegalArgumentException
├── IllegalStateException
├── IndexOutOfBoundsException
│ ├── ArrayIndexOutOfBoundsException
│ └── StringIndexOutOfBoundsException
├── ClassCastException
├── NumberFormatException
├── ConcurrentModificationException
├── UnsupportedOperationException
└── ... и многие другие
Проблема 1: NullPointerException (NPE)
Реальный случай: забыл null проверку
@Service
public class UserService {
private final UserRepository repository;
public User getUser(Long id) {
User user = repository.findById(id); // может вернуть null
return user.toDto(); // ❌ NPE если user == null!
}
}
// Лучше:
public UserDto getUser(Long id) {
User user = repository.findById(id);
if (user == null) {
throw new UserNotFoundException("User not found with id: " + id);
}
return user.toDto();
}
// Или с Optional:
public UserDto getUser(Long id) {
return repository.findById(id)
.map(User::toDto)
.orElseThrow(() -> new UserNotFoundException("User " + id + " not found"));
}
Как мы ловим в реальности
@RestController
@RequestMapping("/api/users")
public class UserController {
private final UserService service;
@GetMapping("/{id}")
public ResponseEntity<UserDto> getUser(@PathVariable Long id) {
try {
UserDto user = service.getUser(id);
return ResponseEntity.ok(user);
} catch (NullPointerException e) {
// НЕ логируем NPE специально - это ошибка кода!
log.error("BUG: NullPointerException in getUser", e);
return ResponseEntity.internalServerError().build();
} catch (UserNotFoundException e) {
log.warn("User not found: {}", id);
return ResponseEntity.notFound().build();
}
}
}
// Лучше: использовать @ExceptionHandler
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(NullPointerException.class)
public ResponseEntity<ErrorResponse> handleNpe(NullPointerException e) {
log.error("BUG: NullPointerException - this is a code bug!", e);
return ResponseEntity.status(500).body(
new ErrorResponse("Internal server error", HttpStatus.INTERNAL_SERVER_ERROR)
);
}
@ExceptionHandler(UserNotFoundException.class)
public ResponseEntity<ErrorResponse> handleUserNotFound(UserNotFoundException e) {
return ResponseEntity.status(404).body(
new ErrorResponse(e.getMessage(), HttpStatus.NOT_FOUND)
);
}
}
Проблема 2: IllegalArgumentException
Реальный случай: неправильный параметр
@Service
public class PaymentService {
public void processPayment(BigDecimal amount, String currency) {
if (amount == null || amount.signum() <= 0) {
throw new IllegalArgumentException(
"Amount must be positive, got: " + amount
);
}
if (currency == null || currency.isEmpty()) {
throw new IllegalArgumentException(
"Currency cannot be null or empty"
);
}
if (!isValidCurrency(currency)) {
throw new IllegalArgumentException(
"Unknown currency: " + currency + ". Supported: USD, EUR, GBP"
);
}
// Обработка платежа
processTransaction(amount, currency);
}
private boolean isValidCurrency(String currency) {
return currency.matches("^[A-Z]{3}$"); // ISO 4217 код
}
}
// Использование
paymentService.processPayment(
new BigDecimal("99.99"), // OK
"USD" // OK
);
paymentService.processPayment(
new BigDecimal("-50"), // ❌ IllegalArgumentException
"USD"
);
paymentService.processPayment(
new BigDecimal("100"),
"INVALID" // ❌ IllegalArgumentException
);
Проблема 3: ArrayIndexOutOfBoundsException
Реальный случай: ошибка в цикле
@Service
public class DataProcessingService {
// ❌ Плохо: может быть ArrayIndexOutOfBoundsException
public void processArray(int[] data) {
for (int i = 0; i <= data.length; i++) { // <= вместо <
System.out.println(data[i]); // ArrayIndexOutOfBoundsException!
}
}
// ✅ Правильно: нормальный цикл
public void processArrayCorrect(int[] data) {
for (int i = 0; i < data.length; i++) { // правильное условие
System.out.println(data[i]);
}
}
// ✅ Ещё лучше: for-each
public void processArrayBest(int[] data) {
for (int value : data) {
System.out.println(value);
}
}
// ✅ Или Stream
public void processArrayStream(int[] data) {
Arrays.stream(data)
.forEach(System.out::println);
}
}
Проблема 4: ClassCastException
Реальный случай: неправильная приведение типа
@Service
public class DataService {
// ❌ Плохо: может быть ClassCastException
public String processData(Object data) {
String str = (String) data; // если data не String -> ошибка!
return str.toUpperCase();
}
// ✅ Правильно: проверка типа
public String processDataSafe(Object data) {
if (data instanceof String) {
String str = (String) data; // теперь безопасно
return str.toUpperCase();
} else {
throw new IllegalArgumentException(
"Expected String, got: " + data.getClass().getName()
);
}
}
// ✅ Java 16+: pattern matching
public String processDataPattern(Object data) {
if (data instanceof String str) { // pattern matching
return str.toUpperCase();
} else {
throw new IllegalArgumentException(
"Expected String, got: " + data.getClass().getName()
);
}
}
}
Проблема 5: NumberFormatException
Реальный случай: парсинг числа
@RestController
public class ApiController {
// ❌ Плохо: NumberFormatException если строка не число
@GetMapping("/numbers/{id}")
public ResponseEntity<Integer> getNumber(@PathVariable String id) {
int number = Integer.parseInt(id); // может быть ошибка
return ResponseEntity.ok(number);
}
// ✅ Правильно: обработка исключения
@GetMapping("/numbers/{id}")
public ResponseEntity<Integer> getNumberSafe(@PathVariable String id) {
try {
int number = Integer.parseInt(id);
return ResponseEntity.ok(number);
} catch (NumberFormatException e) {
return ResponseEntity.badRequest().build();
}
}
// ✅ Ещё лучше: валидация параметра
@GetMapping("/numbers/{id}")
public ResponseEntity<Integer> getNumberBest(
@PathVariable @Min(0) @Max(Integer.MAX_VALUE) Integer id) {
return ResponseEntity.ok(id);
}
}
Проблема 6: ConcurrentModificationException
Реальный случай: изменение коллекции во время итерации
@Service
public class ListProcessingService {
// ❌ Плохо: ConcurrentModificationException
public void removeInactive(List<User> users) {
for (User user : users) {
if (!user.isActive()) {
users.remove(user); // ❌ модифицируем во время итерации
}
}
}
// ✅ Правильно: использовать Iterator
public void removeInactiveSafe(List<User> users) {
Iterator<User> iterator = users.iterator();
while (iterator.hasNext()) {
User user = iterator.next();
if (!user.isActive()) {
iterator.remove(); // правильно
}
}
}
// ✅ Ещё лучше: создать новый список
public void removeInactiveBest(List<User> users) {
users.removeIf(user -> !user.isActive());
}
// ✅ Или с Stream
public List<User> getActive(List<User> users) {
return users.stream()
.filter(User::isActive)
.collect(toList());
}
}
Проблема 7: IllegalStateException
Реальный случай: неправильное состояние объекта
public class Order {
private OrderStatus status;
private LocalDateTime shippedDate;
// ❌ Плохо: не проверяем состояние
public void shipOrder(LocalDateTime date) {
this.shippedDate = date; // может быть в любом статусе
this.status = OrderStatus.SHIPPED;
}
// ✅ Правильно: проверяем состояние
public void shipOrder(LocalDateTime date) {
if (this.status != OrderStatus.PENDING) {
throw new IllegalStateException(
"Order can only be shipped from PENDING state, current: " + status
);
}
this.shippedDate = date;
this.status = OrderStatus.SHIPPED;
}
// ❌ Плохо: нет проверки
public void cancel() {
this.status = OrderStatus.CANCELLED; // можно отменить любой заказ?
}
// ✅ Правильно: проверяем
public void cancel() {
if (this.status == OrderStatus.SHIPPED) {
throw new IllegalStateException(
"Cannot cancel SHIPPED order"
);
}
this.status = OrderStatus.CANCELLED;
}
}
Best Practices при работе с непроверяемыми исключениями
1. Не ловить RuntimeException без причины
// ❌ Плохо: ловим все и молчим
try {
// код
} catch (RuntimeException e) {
// молчим
}
// ✅ Правильно: ловим специфичные исключения
try {
int number = Integer.parseInt(input);
} catch (NumberFormatException e) {
log.warn("Invalid number format: {}", input);
return null;
}
2. Проверяй параметры методов
// ❌ Плохо: нет проверок
public void transfer(BigDecimal amount, Account from, Account to) {
from.withdraw(amount);
to.deposit(amount);
}
// ✅ Правильно: проверяем параметры
public void transfer(BigDecimal amount, Account from, Account to) {
if (amount == null || amount.signum() <= 0) {
throw new IllegalArgumentException("Amount must be positive");
}
if (from == null) {
throw new IllegalArgumentException("Source account cannot be null");
}
if (to == null) {
throw new IllegalArgumentException("Target account cannot be null");
}
if (from.equals(to)) {
throw new IllegalArgumentException("Accounts cannot be the same");
}
from.withdraw(amount);
to.deposit(amount);
}
// ✅ Или используй @NotNull, @NotEmpty и т.д.
public void transfer(
@NotNull @DecimalMin("0.01") BigDecimal amount,
@NotNull Account from,
@NotNull Account to) {
// бизнес-логика
}
3. Создавай свои исключения
// ❌ Плохо: используем стандартные
throw new IllegalArgumentException("User not found");
// ✅ Правильно: свои исключения
public class UserNotFoundException extends RuntimeException {
public UserNotFoundException(String message) {
super(message);
}
public UserNotFoundException(String message, Throwable cause) {
super(message, cause);
}
}
public class InvalidOrderStateException extends RuntimeException {
public InvalidOrderStateException(String message) {
super(message);
}
}
// Использование
throw new UserNotFoundException("User not found with id: " + id);
Статистика непроверяемых исключений в моей практике
Топ-5 непроверяемых исключений которые я фиксил:
1. NullPointerException ~40%
(обычно забыли проверку null)
2. IllegalArgumentException ~25%
(неправильные параметры методов)
3. NumberFormatException ~15%
(парсинг числовых строк)
4. IllegalStateException ~12%
(неправильное состояние объекта)
5. ConcurrentModificationException ~8%
(изменение коллекции во время итерации)
Вывод
Непроверяемые исключения (RuntimeException) - это:
- Признак ошибки в коде, не ошибки в данных
- НЕ требуют явной обработки (в отличие от checked)
- Должны предотвращаться (валидация входных данных)
- Помогают отлавливать bugs если их не ловить
- Часто игнорируются без нужной причины
Практический совет: Не ловите RuntimeException - лучше предотвращайте их через валидацию и проверки. Если RuntimeException всё же вылетела - это признак bugs в коде, а не нормальный ход выполнения.