Частые исключения в Java: причины и как исправить
Строка int apples = stock.get("apple"); выглядит безопасной: метод get() вызывается у существующей карты. Но программа падает с NullPointerException — ключа "apple" в карте нет, get() вернул null, а автораспаковка в int незаметно вызвала у него intValue().
import java.util.HashMap;
import java.util.Map;
public class StockDemo {
public static void main(String[] args) {
Map<String, Integer> stock = new HashMap<>();
stock.put("pear", 5);
int apples = stock.get("apple");
System.out.println(apples);
}
} Exception in thread "main" java.lang.NullPointerException: Cannot invoke "java.lang.Integer.intValue()" because the return value of "java.util.Map.get(Object)" is null
at StockDemo.main(StockDemo.java:8) Частые исключения в Java — это несколько непроверяемых (unchecked) исключений, которые JVM и стандартная библиотека выбрасывают при ошибках в логике программы: NullPointerException (обращение к null), ArrayIndexOutOfBoundsException (индекс за пределами массива), ClassCastException (недопустимое приведение типа), NumberFormatException (строка не является числом), IllegalArgumentException и IllegalStateException (недопустимый аргумент или состояние объекта) и ConcurrentModificationException (изменение коллекции во время перебора). Почти всегда их исправляют не блоком catch, а правкой кода: проверкой на null, правильной границей цикла, instanceof перед приведением.
Сводная таблица частых исключений
Все исключения из этого урока — непроверяемые наследники RuntimeException: компилятор не требует их обрабатывать, и обнаруживаются они только во время выполнения. Все они, кроме ConcurrentModificationException из пакета java.util, лежат в пакете java.lang и доступны без import.
| Исключение | Пакет | Тип | Типичная причина | Как исправить |
|---|---|---|---|---|
NullPointerException | java.lang | unchecked | Вызов метода, обращение к полю, элементу массива или автораспаковка у null | Проверка на null, getOrDefault(), Objects.requireNonNullElse() |
ArrayIndexOutOfBoundsException | java.lang | unchecked | Индекс меньше 0 или не меньше длины массива, чаще всего i <= arr.length | i < arr.length, цикл for-each, проверка args.length |
StringIndexOutOfBoundsException, IndexOutOfBoundsException | java.lang | unchecked | charAt()/substring() за пределами строки, list.get(i) при i >= size() | Сверять индекс с length() или size() |
ClassCastException | java.lang | unchecked | Приведение объекта к типу, которым он на самом деле не является | instanceof с pattern matching, generics без raw types |
NumberFormatException | java.lang | unchecked | Integer.parseInt() или Double.parseDouble() получил не число: "abc", " 42", "3,14" | strip(), try-catch на границе ввода, Scanner.hasNextInt() |
IllegalArgumentException | java.lang | unchecked | Метод получил недопустимое значение аргумента | Проверить или нормализовать значение до вызова |
IllegalStateException | java.lang | unchecked | Метод вызван не вовремя: поток Stream уже использован, Scanner закрыт | Соблюдать порядок вызовов, создавать объект заново |
ConcurrentModificationException | java.util | unchecked | Удаление или добавление элементов коллекции внутри for-each | removeIf(), Iterator.remove() |
ArithmeticException | java.lang | unchecked | Целочисленное деление на ноль: / by zero | Проверить делитель перед делением |
UnsupportedOperationException | java.lang | unchecked | add() или remove() у неизменяемой коллекции, например List.of(...) | Скопировать в изменяемую: new ArrayList<>(List.of(...)) |
Почему эти исключения непроверяемые и где они стоят в дереве классов, рассказано в уроке Иерархия исключений в Java.
Что ловить, а что исправлять
Перехватывать в catch оправданно обычно только NumberFormatException и IllegalArgumentException — там, где в программу приходят данные от пользователя, из файла или сети. NullPointerException, ArrayIndexOutOfBoundsException, ClassCastException и ConcurrentModificationException означают баг в коде: catch его только спрячет. Чем опасно «проглоченное» исключение — в разделе Типичные промахи новичков.
NullPointerException: обращение к null
NullPointerException (NPE) выбрасывается, когда программа обращается к объекту через ссылку, равную null: вызывает у неё метод, читает или записывает поле, берёт элемент или длину массива, распаковывает null в примитив. Начиная с Java 15 сообщение JVM прямо называет операцию и выражение, которое оказалось null.
Типичные причины NullPointerException
Сообщения в таблице — для кода, скомпилированного в IDE или сборщиком (Maven, Gradle), где имена переменных сохраняются; как выглядит сообщение без них, показано ниже.
| Что делает код | Пример | Сообщение JVM (Java 15+) |
|---|---|---|
Вызывает метод у null | name.length() | Cannot invoke "String.length()" because "name" is null |
| Читает поле | user.name | Cannot read field "name" because "user" is null |
| Записывает поле | user.name = "Анна" | Cannot assign field "name" because "user" is null |
| Берёт элемент массива | arr[0] при int[] arr = null | Cannot load from int array because "arr" is null |
| Берёт длину массива | arr.length | Cannot read the array length because "arr" is null |
Распаковывает null в примитив | int n = map.get("x"); | Cannot invoke "java.lang.Integer.intValue()" because the return value of "java.util.Map.get(Object)" is null |
| Использует неинициализированное поле | this.title.trim() | Cannot invoke "String.trim()" because "this.title" is null |
Ещё два источника NPE — throw ссылки, равной null (разобрано в разделе Что можно и нельзя бросать через throw), и synchronized по null.
Пример: helpful NullPointerException с javac и javac -g
public class NpeDemo {
public static void main(String[] args) {
String name = null;
System.out.println(name.length());
}
} Компиляция и запуск из командной строки без дополнительных ключей:
javac NpeDemo.java
java NpeDemo Exception in thread "main" java.lang.NullPointerException: Cannot invoke "String.length()" because "<local1>" is null
at NpeDemo.main(NpeDemo.java:4) Если скомпилировать класс с ключом -g (javac -g NpeDemo.java), вместо <local1> появится настоящее имя переменной:
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "String.length()" because "name" is null
at NpeDemo.main(NpeDemo.java:4) Откуда берётся <local1>
Имена локальных переменных хранятся в class-файле только в отладочной информации, которую javac записывает с ключом -g. Без неё JVM знает лишь номер слота: в статическом main слот 0 занимает args, поэтому первая объявленная переменная — <local1>. IntelliJ IDEA, Maven и Gradle по умолчанию компилируют с отладочной информацией, так что в IDE вы увидите имя переменной. Имена полей ("this.title") и методов в сообщении есть всегда.
Подробные сообщения NPE (helpful NullPointerExceptions, JEP 358) появились в Java 14 и включены по умолчанию начиная с Java 15. В Java 8 и 11 то же исключение выводится без текста — только java.lang.NullPointerException и номер строки.
Как исправить NullPointerException
Найдите в сообщении выражение, которое оказалось null, и решите: значение там законно может отсутствовать (тогда подставьте значение по умолчанию или проверьте его) или null появился из-за ошибки выше по коду (тогда исправьте место, где ссылку забыли инициализировать).
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
public class NpeFixDemo {
public static void main(String[] args) {
Map<String, Integer> stock = new HashMap<>();
stock.put("pear", 5);
int apples = stock.getOrDefault("apple", 0);
System.out.println("Яблок: " + apples);
String city = null;
System.out.println("Москва".equals(city));
System.out.println(Objects.requireNonNullElse(city, "не указан"));
if (city != null) {
System.out.println(city.length());
}
}
} Яблок: 0
false
не указан getOrDefault()вместоget(), если в карте может не быть ключа, — так исправляется пример из начала урока.- Константа слева:
"Москва".equals(city)возвращаетfalseдляnull, аcity.equals("Москва")выбросит NPE. Objects.requireNonNullElse(value, default)(Java 9+) подставляет значение по умолчанию.Optionalв качестве возвращаемого типа метода явно говорит вызывающему коду, что результата может не быть.Objects.requireNonNull(arg, "arg")в начале метода выбрасывает NPE сразу при входе, с понятным сообщением, а не через десять вызовов — см. таблицу стандартных исключений в разделе Оператор throw и throw new.
ArrayIndexOutOfBoundsException и другие IndexOutOfBounds
ArrayIndexOutOfBoundsException выбрасывается при обращении к элементу массива по индексу меньше 0 или не меньше его длины: у массива из трёх элементов допустимы только индексы 0, 1 и 2. Сообщение в Java 11+ содержит и индекс, и длину: Index 3 out of bounds for length 3.
public class AioobeDemo {
public static void main(String[] args) {
int[] numbers = {10, 20, 30};
for (int i = 0; i <= numbers.length; i++) {
System.out.println(numbers[i]);
}
}
} 10
20
30
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3
at AioobeDemo.main(AioobeDemo.java:5) Типичные причины:
<=вместо<в условии цикла, как в примере, или обращениеarr[arr.length]вместоarr[arr.length - 1]к последнему элементу.- Нумерация с 1: цикл
for (int i = 1; i <= n; i++), перенесённый из математической записи. - Отрицательный индекс после вычислений:
Index -1 out of bounds for length 3. - Пустой массив
args:args[0]при запуске программы без аргументов даётIndex 0 out of bounds for length 0.
StringIndexOutOfBoundsException и IndexOutOfBoundsException
ArrayIndexOutOfBoundsException и StringIndexOutOfBoundsException — подклассы IndexOutOfBoundsException, поэтому catch (IndexOutOfBoundsException e) перехватит все три.
| Исключение | Где возникает | Пример | Сообщение |
|---|---|---|---|
ArrayIndexOutOfBoundsException | Обращение к элементу массива | arr[3] при int[] arr = new int[3] | Index 3 out of bounds for length 3 |
StringIndexOutOfBoundsException | Методы String: charAt(), substring() | "Java".charAt(10) | Содержит индекс и длину строки; точный текст отличается в разных версиях JDK |
IndexOutOfBoundsException | Методы списков: get(), set(), remove(int) | new ArrayList<String>().get(0) | Index 0 out of bounds for length 0 |
import java.util.ArrayList;
import java.util.List;
public class ListIndexDemo {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
System.out.println(list.size());
System.out.println(list.get(0));
}
} 0
Exception in thread "main" java.lang.IndexOutOfBoundsException: Index 0 out of bounds for length 0
...
at ListIndexDemo.main(ListIndexDemo.java:8) Как исправить ArrayIndexOutOfBoundsException
Сравнивайте индекс с длиной строго через <, а если индекс в теле цикла не нужен — используйте for-each, в котором выйти за границы невозможно. Внешние данные (аргументы, ввод пользователя) проверяйте перед обращением по индексу.
public class AioobeFixDemo {
public static void main(String[] args) {
int[] numbers = {10, 20, 30};
for (int n : numbers) {
System.out.println(n);
}
String first = args.length > 0 ? args[0] : "нет аргументов";
System.out.println(first);
}
} Вывод при запуске без аргументов:
10
20
30
нет аргументов ClassCastException: недопустимое приведение типа
ClassCastException выбрасывается, когда объект приводят к классу или интерфейсу, которым он на самом деле не является. Компилятор пропускает такое приведение, если статический тип переменной (Object, суперкласс, интерфейс) в принципе может содержать нужный тип, а реальный объект проверяется только во время выполнения.
public class ClassCastDemo {
public static void main(String[] args) {
Object value = 42;
String text = (String) value;
System.out.println(text);
}
} Exception in thread "main" java.lang.ClassCastException: class java.lang.Integer cannot be cast to class java.lang.String (java.lang.Integer and java.lang.String are in module java.base of loader 'bootstrap')
at ClassCastDemo.main(ClassCastDemo.java:4) Сообщение читается так: реальный класс объекта — java.lang.Integer, а приводили его к java.lang.String; в скобках указано, из какого модуля и загрузчика взяты оба класса. Для ваших собственных классов там будет are in unnamed module of loader 'app'. Приведение заведомо несовместимых типов, например (String) 42, вообще не скомпилируется: incompatible types: int cannot be converted to String.
ClassCastException без явного приведения
Что выведет этот код? В нём нет ни одного оператора приведения, но компилятор вставляет его сам при чтении из generic-коллекции.
import java.util.ArrayList;
import java.util.List;
public class HiddenCastDemo {
public static void main(String[] args) {
List raw = new ArrayList();
raw.add(42);
List<String> names = raw;
System.out.println("Размер: " + names.size());
String first = names.get(0);
}
} Размер: 1
Exception in thread "main" java.lang.ClassCastException: class java.lang.Integer cannot be cast to class java.lang.String (java.lang.Integer and java.lang.String are in module java.base of loader 'bootstrap')
at HiddenCastDemo.main(HiddenCastDemo.java:10) Число попало в List<String> через «сырой» тип List без параметра. Компилятор предупредил об этом (uses unchecked or unsafe operations), но код собрался, а исключение возникло гораздо позже — в строке 10, при чтении элемента. Не используйте raw types и не игнорируйте предупреждения unchecked.
Как исправить ClassCastException
Перед приведением проверяйте тип оператором instanceof (подробнее — в уроке «Приведение ссылочных типов в Java»). С Java 16 проверка и приведение записываются одной конструкцией — pattern matching: if (a instanceof Dog dog).
public class AnimalCastDemo {
static class Animal { }
static class Dog extends Animal {
void bark() {
System.out.println("Гав!");
}
}
static class Cat extends Animal { }
public static void main(String[] args) {
Animal[] animals = {new Dog(), new Cat()};
for (Animal a : animals) {
if (a instanceof Dog dog) {
dog.bark();
} else {
System.out.println("Не собака: " + a.getClass().getSimpleName());
}
}
}
} Гав!
Не собака: Cat Без проверки приведение (Dog) a для кошки закончилось бы сообщением class AnimalCastDemo$Cat cannot be cast to class AnimalCastDemo$Dog. Если же приходится проверять тип в длинной цепочке if-else, это повод заменить её полиморфным методом в базовом классе.
NumberFormatException: For input string
NumberFormatException выбрасывают методы Integer.parseInt(), Long.parseLong(), Double.parseDouble() и valueOf(), когда строка не является корректной записью числа. Сообщение For input string: "..." показывает исходную строку в кавычках — по ним видно даже лишние пробелы. Это подкласс IllegalArgumentException.
public class NfeDemo {
public static void main(String[] args) {
String input = "abc";
int number = Integer.parseInt(input);
System.out.println(number);
}
} Exception in thread "main" java.lang.NumberFormatException: For input string: "abc"
...
at NfeDemo.main(NfeDemo.java:4) Чаще всего исключение вызывают строки, которые человеку кажутся числом:
| Вызов | Результат | Почему |
|---|---|---|
Integer.parseInt("42"), Integer.parseInt("+7") | 42, 7 | Корректная запись, знак + допустим |
Integer.parseInt(" 42") | For input string: " 42" | Пробелы не отбрасываются автоматически |
Integer.parseInt("3.14") | For input string: "3.14" | Дробное число для int |
Double.parseDouble("3,14") | For input string: "3,14" | parseDouble понимает только точку, независимо от локали |
Integer.parseInt("3000000000") | For input string: "3000000000" | Больше Integer.MAX_VALUE (2147483647); нужен Long.parseLong() |
Integer.parseInt("") | For input string: "" | Пустая строка, например пустое поле формы |
Integer.parseInt(null) | NumberFormatException, а не NPE | Текст сообщения для null отличается в разных версиях JDK |
Как исправить NumberFormatException
Ввод пользователя нельзя заранее сделать корректным, поэтому здесь try-catch уместен — но в одном месте, на границе ввода, с понятным результатом вместо «магического» нуля. Пробелы по краям убирает strip() (Java 11+), а при чтении из консоли можно заранее спросить scanner.hasNextInt().
import java.util.OptionalInt;
public class SafeParseDemo {
static OptionalInt parseInt(String s) {
if (s == null) {
return OptionalInt.empty();
}
try {
return OptionalInt.of(Integer.parseInt(s.strip()));
} catch (NumberFormatException e) {
return OptionalInt.empty();
}
}
public static void main(String[] args) {
String[] inputs = {"42", " 7 ", "abc", "3.14", null};
for (String s : inputs) {
System.out.println(s + " -> " + parseInt(s));
}
}
} 42 -> OptionalInt[42]
7 -> OptionalInt[7]
abc -> OptionalInt.empty
3.14 -> OptionalInt.empty
null -> OptionalInt.empty Вызывающий код сам решает, что делать с пустым результатом: попросить ввести число ещё раз, подставить значение по умолчанию или сообщить об ошибке.
IllegalArgumentException: недопустимый аргумент
IllegalArgumentException означает, что метод получил значение аргумента, с которым не может работать: отрицательный размер, неизвестное имя константы, число вне диапазона. Его выбрасывают методы JDK, и его же принято выбрасывать в собственном коде при проверке параметров. Текст сообщения задаёт автор метода.
| Вызов JDK | Сообщение |
|---|---|
new ArrayList<>(-1) | Illegal Capacity: -1 |
"ab".repeat(-1) | count is negative: -1 |
DayOfWeek.valueOf("monday") | No enum constant java.time.DayOfWeek.monday |
Integer.parseInt("abc") | For input string: "abc" (подкласс NumberFormatException) |
Самый частый случай на практике — valueOf() у перечисления: имя константы сравнивается с учётом регистра и пробелов.
import java.time.DayOfWeek;
public class IaeDemo {
public static void main(String[] args) {
String input = "monday";
DayOfWeek day = DayOfWeek.valueOf(input);
System.out.println(day);
}
} Exception in thread "main" java.lang.IllegalArgumentException: No enum constant java.time.DayOfWeek.monday
...
at IaeDemo.main(IaeDemo.java:6) Как исправить IllegalArgumentException
Прочитайте сообщение и документацию метода: в ней перечислены допустимые значения. Входные данные приведите к нужному виду до вызова, а если они приходят от пользователя и могут быть любыми, перехватите исключение на границе ввода.
import java.time.DayOfWeek;
import java.util.Locale;
public class IaeFixDemo {
public static void main(String[] args) {
String input = " monday ";
DayOfWeek day = DayOfWeek.valueOf(input.strip().toUpperCase(Locale.ROOT));
System.out.println(day);
}
} MONDAY Locale.ROOT в toUpperCase() защищает от сюрпризов локали: в турецкой локали строчная i превращается в İ с точкой, и "friday" перестало бы совпадать с FRIDAY. Как выбрасывать IllegalArgumentException в своём методе, показано в разделе Оператор throw и throw new.
IllegalStateException: метод вызван не вовремя
IllegalStateException выбрасывается, когда аргументы метода в порядке, но объект находится в состоянии, в котором этот метод вызывать нельзя: поток Stream уже использован, Scanner закрыт, итератор ещё не вернул элемент. Ошибка не в данных, а в порядке вызовов.
import java.util.stream.Stream;
public class StreamReuseDemo {
public static void main(String[] args) {
Stream<String> names = Stream.of("Анна", "Борис");
names.forEach(System.out::println);
System.out.println(names.count());
}
} Анна
Борис
Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed
...
at StreamReuseDemo.main(StreamReuseDemo.java:7) Другие методы JDK, которые выбрасывают это исключение:
| Ситуация | Сообщение |
|---|---|
Повторная терминальная операция над тем же Stream | stream has already been operated upon or closed |
scanner.nextLine() после scanner.close() | Scanner closed |
matcher.group() до успешного matcher.find() или matches() | No match found |
iterator.remove() до next() или дважды подряд | Без сообщения |
Как исправить IllegalStateException
Соблюдайте жизненный цикл объекта: не используйте его после закрытия и вызывайте методы в том порядке, который требует документация. Поток Stream одноразовый — храните исходные данные в коллекции и создавайте из неё новый поток для каждой операции.
import java.util.List;
public class StreamFixDemo {
public static void main(String[] args) {
List<String> names = List.of("Анна", "Борис");
names.forEach(System.out::println);
System.out.println(names.stream().count());
}
} Анна
Борис
2 Чем IllegalStateException отличается от IllegalArgumentException при выборе исключения для своего кода, разобрано в разделе Когда создавать свое исключение, а когда взять стандартное.
ConcurrentModificationException: изменение коллекции при обходе
ConcurrentModificationException (пакет java.util) выбрасывает итератор ArrayList, HashMap и других стандартных коллекций, если коллекцию изменили в обход итератора, пока он её перебирал. Самый частый случай — вызов list.remove() или list.add() внутри цикла for-each по тому же списку.
import java.util.ArrayList;
import java.util.List;
public class CmeDemo {
public static void main(String[] args) {
List<String> names = new ArrayList<>(List.of("Анна", "Борис", "Вера"));
for (String name : names) {
if (name.startsWith("А")) {
names.remove(name);
}
}
System.out.println(names);
}
} Exception in thread "main" java.util.ConcurrentModificationException
at java.base/java.util.ArrayList$Itr.checkForComodification(...)
at java.base/java.util.ArrayList$Itr.next(...)
at CmeDemo.main(CmeDemo.java:7) Исключение возникает не в строке с remove(), а в строке 7 — заголовке цикла: for-each вызывает iterator.next(), а тот замечает, что счётчик изменений списка (modCount) уже не совпадает с ожидаемым. Сообщения у этого исключения нет.
Что выведет этот код?
Тот же цикл, но удаляем не первый элемент, а «Борис»:
for (String name : names) {
if (name.startsWith("Б")) {
names.remove(name);
}
}
System.out.println(names); [Анна, Вера] Исключения нет, хотя код такой же неправильный. После удаления предпоследнего элемента размер списка стал 2, курсор итератора тоже равен 2, и hasNext() возвращает false — цикл завершается, не дойдя до проверки в next(). «Вера» при этом так и не была проверена условием. Итераторы стандартных коллекций работают по принципу fail-fast «по возможности»: отсутствие исключения не доказывает, что код корректен.
Как исправить ConcurrentModificationException
Удаляйте элементы методом removeIf() (Java 8+) или через remove() самого итератора — оба способа обновляют состояние итератора вместе со списком.
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class CmeFixDemo {
public static void main(String[] args) {
List<String> names = new ArrayList<>(List.of("Анна", "Борис", "Вера"));
names.removeIf(name -> name.startsWith("А"));
System.out.println(names);
Iterator<String> it = names.iterator();
while (it.hasNext()) {
if (it.next().startsWith("В")) {
it.remove();
}
}
System.out.println(names);
}
} [Борис, Вера]
[Борис] Не только про потоки
Слово «Concurrent» в названии сбивает с толку: все примеры выше однопоточные. Если же коллекцию действительно изменяют и читают несколько потоков, обычный ArrayList или HashMap не подходит вовсе — берите CopyOnWriteArrayList или ConcurrentHashMap из java.util.concurrent: их итераторы ConcurrentModificationException не выбрасывают.
Как читать сообщение и не допускать этих исключений
Сообщение об исключении состоит из трёх частей: полное имя класса исключения, текст после двоеточия и стек вызовов. Имя класса говорит о категории ошибки, текст — о конкретном значении (индексе, строке, выражении, равном null), а первая строка at с вашим классом указывает файл и номер строки. Как устроен стек вызовов, разобрано в уроке Обработка исключений в Java.
| Фрагмент сообщения | Что искать в коде |
|---|---|
because "x" is null, because the return value of "...get(Object)" is null | Откуда в x или в результат метода пришёл null |
Index 3 out of bounds for length 3 | Индекс равен длине: условие <= или лишний + 1 |
class A cannot be cast to class B | Где объект класса A попал в переменную, которую считают B |
For input string: " 42" | Кавычки показывают пробелы, запятые и пустую строку во вводе |
ConcurrentModificationException без текста | add()/remove() у коллекции внутри её же for-each |
Чтобы эти исключения не появлялись вовсе:
- Проверяйте аргументы в начале метода (
Objects.requireNonNull(),throw new IllegalArgumentException(...)) — ошибка проявится там, где появились плохие данные, а не в глубине вызовов. - Предпочитайте конструкции, в которых ошибиться нельзя: for-each вместо индексного цикла,
removeIf()вместо удаления в цикле, pattern matchinginstanceofвместо голого приведения,getOrDefault()вместоget(). - Не перехватывайте исключения-баги:
catch (NullPointerException e)илиcatch (Exception e)«на всякий случай» прячет причину. Как правильно строитьtry-catchдля ожидаемых ошибок — в уроке Оператор try-catch-finally в Java. - Не игнорируйте предупреждения компилятора и IDE:
unchecked, «Possible NullPointerException», «Array index is out of bounds» часто указывают на будущее исключение ещё до запуска.
Часто задаваемые вопросы
java.lang.NullPointerException: как исправить за три шага?
Первое — найдите в стеке вызовов первую строку at с вашим классом: там файл и номер строки. Второе — прочитайте в сообщении, какое выражение оказалось null (в Java 15+ оно названо после слова because). Третье — выясните, должно ли значение там быть: если да, исправьте место, где ссылку не инициализировали; если оно законно может отсутствовать, добавьте проверку или значение по умолчанию. Приёмы с примерами — в разделе Как исправить NullPointerException.
Как получить подробное сообщение NullPointerException в Java 8 или 11?
Никак: helpful NullPointerExceptions появились только в Java 14. В Java 14 их включает флаг -XX:+ShowCodeDetailsInExceptionMessages, с Java 15 они работают по умолчанию (отключить можно флагом -XX:-ShowCodeDetailsInExceptionMessages). На Java 8 и 11 остаётся номер строки из стека вызовов — поэтому полезно не писать длинные цепочки вида a.getB().getC().getD() в одной строке.
Почему catch (IllegalArgumentException e) перехватывает NumberFormatException?
Потому что NumberFormatException — подкласс IllegalArgumentException, а catch перехватывает указанный класс и всех его наследников. По той же причине catch (IndexOutOfBoundsException e) поймает и ArrayIndexOutOfBoundsException, и StringIndexOutOfBoundsException. Если нужны оба блока, catch подкласса должен стоять первым, иначе код не скомпилируется.
Почему List.of(...).add() выбрасывает UnsupportedOperationException?
List.of(), Set.of(), Map.of() (Java 9+), Collections.unmodifiableList() и stream().toList() (Java 16+) возвращают неизменяемые коллекции: любая попытка add(), remove() или set() выбрасывает UnsupportedOperationException без сообщения. Arrays.asList() разрешает set(), но не add() и remove(). Если список нужно менять, скопируйте его: new ArrayList<>(List.of(...)).
Как удалить элементы из HashMap во время перебора без ConcurrentModificationException?
Используйте removeIf() у представлений карты: map.entrySet().removeIf(e -> e.getValue() == 0), map.keySet().removeIf(...) или map.values().removeIf(...). Другой способ — явный Iterator по entrySet() и вызов iterator.remove(). Вызов map.remove(key) внутри for-each по keySet() приводит к тому же исключению, что и со списком.
Чем StackOverflowError и OutOfMemoryError отличаются от этих исключений?
Это не исключения, а ошибки: наследники Error, а не Exception. Они сигнализируют о проблеме уровня JVM (переполнен стек вызовов, закончилась память), их не перехватывает catch (Exception e), и обычно их не обрабатывают в коде. Разбор с примером — в разделе catch (Exception e) и StackOverflowError.
Комментарии