throw и throws в Java: разница и примеры
Одна буква меняет смысл: throw выбрасывает исключение, а throws только предупреждает о нём. Уберите throws IllegalAccessException из заголовка метода, внутри которого стоит throw new IllegalAccessException("demo"), — и программа не скомпилируется: error: unreported exception IllegalAccessException; must be caught or declared to be thrown.
throw в Java — оператор, который выбрасывает конкретный объект исключения (экземпляр класса Throwable или его подкласса) и прерывает обычный ход выполнения: управление переходит к ближайшему подходящему блоку catch. throws — часть объявления метода или конструктора, где перечисляются типы исключений, которые он может передать вызывающему коду; для проверяемых (checked) исключений такое объявление обязательно.
Чем throw отличается от throws
throw — исполняемая инструкция в теле метода: она выбрасывает исключение прямо в момент выполнения. throws — декларация в сигнатуре метода: сама ничего не выбрасывает, а сообщает компилятору и вызывающему коду, какие исключения метод может пропустить наружу. Обычно они работают в паре: throw внутри метода, throws в его заголовке.
| Критерий | throw | throws |
|---|---|---|
| Что это | Оператор (инструкция) | Часть объявления метода или конструктора |
| Где пишется | В теле метода, в любом блоке | В сигнатуре, после списка параметров |
| Что указывается | Один объект: throw new IOException("...") | Один или несколько классов через запятую: throws IOException, ParseException |
| Что делает | Выбрасывает исключение и прерывает выполнение | Ничего не выполняет: только объявляет, что исключение может выйти из метода |
| Когда обязателен | Когда программа сама должна сообщить об ошибке | Когда checked-исключение не перехвачено внутри метода |
Оба ключевых слова в одном методе:
public void readConfig() throws IOException { // throws: объявление
throw new IOException("Файл config.txt не найден"); // throw: выброс
} Оператор throw и throw new: как выбросить исключение
Исключение в Java выбрасывают инструкцией throw, после которой стоит объект типа Throwable. Чаще всего объект создаётся тут же оператором new: throw new IllegalArgumentException("сообщение"). После выброса оставшийся код блока не выполняется, а JVM ищет подходящий catch в текущем методе и дальше вверх по стеку вызовов.
Общая форма:
throw throwableInstance; throwableInstance должен быть объектом класса Throwable или производного от него подкласса. Получить такой объект можно двумя способами: создать его оператором new или взять параметр блока catch (об этом — в разделе Повторный выброс исключения).
Пример: метод проверяет аргумент и сам сообщает об ошибке через throw new.
public class ThrowNewExample {
static int divide(int a, int b) {
if (b == 0) {
throw new IllegalArgumentException("Делитель не может быть равен нулю");
}
return a / b;
}
public static void main(String[] args) {
System.out.println(divide(10, 2));
System.out.println(divide(10, 0));
}
} Вывод:
5
Exception in thread "main" java.lang.IllegalArgumentException: Делитель не может быть равен нулю
at ThrowNewExample.divide(ThrowNewExample.java:4)
at ThrowNewExample.main(ThrowNewExample.java:11) IllegalArgumentException — непроверяемое исключение, поэтому throws в объявлении divide() не нужен. Какой стандартный класс выбрать для throw new:
| Исключение | Когда выбрасывать | Пример |
|---|---|---|
IllegalArgumentException | Метод получил недопустимое значение аргумента | throw new IllegalArgumentException("Возраст: " + age) |
IllegalStateException | Аргументы в порядке, но объект в неподходящем состоянии | throw new IllegalStateException("Соединение уже закрыто") |
NullPointerException | Передан null там, где он запрещён | Objects.requireNonNull(name, "name") — выбрасывает его сам |
UnsupportedOperationException | Операция не поддерживается реализацией | throw new UnsupportedOperationException("Только чтение") |
Если ни один стандартный класс не описывает ошибку предметной области, создайте свой — см. урок Создание подклассов исключений.
Полезно знать
Стек вызовов записывается в объект исключения в момент его создания (конструктор Throwable вызывает fillInStackTrace()), а не в момент throw. Если создать исключение в одном методе, а выбросить в другом, трасса укажет на место создания. Поэтому исключение обычно создают прямо в инструкции throw new.
Повторный выброс исключения: throw e
Перехваченное исключение можно выбросить снова инструкцией throw e внутри блока catch. Так делают, когда метод должен отреагировать на ошибку (записать в лог, отменить часть работы), но решение о дальнейшей обработке оставить вызывающему коду. Выбрасывается тот же объект — с исходным сообщением и стеком вызовов.
public class ThrowExample {
public static void doSomething() {
try {
throw new NullPointerException("demo");
} catch (NullPointerException e) {
System.out.println("Перехвачено внутри doSomething.");
throw e; // повторно выбросить исключение
}
}
public static void main(String[] args) {
try {
doSomething();
} catch (NullPointerException e) {
System.out.println("Повторный перехват: " + e);
}
}
} Вывод:
Перехвачено внутри doSomething.
Повторный перехват: java.lang.NullPointerException: demo Если нужно сменить тип исключения, создают новое и передают исходное как причину: throw new IllegalStateException("Не удалось загрузить заказ", e). Почему без второго аргумента теряется исходная ошибка — в разделе Типичные промахи новичков.
Точный повторный выброс (Java 7+)
Начиная с Java 7 компилятор анализирует, какие проверяемые исключения реально может выбросить блок try. Поэтому после catch (Exception e) { throw e; } метод может объявлять не throws Exception, а точный тип:
import java.io.IOException;
public class PreciseRethrow {
static void read(boolean fail) throws IOException {
try {
if (fail) {
throw new IOException("Нет доступа к файлу");
}
System.out.println("Файл прочитан");
} catch (Exception e) {
System.out.println("Лог: " + e.getMessage());
throw e; // компилятор знает: здесь может быть только IOException
}
}
public static void main(String[] args) {
try {
read(false);
read(true);
} catch (IOException e) {
System.out.println("Обработано в main: " + e);
}
}
} Вывод:
Файл прочитан
Лог: Нет доступа к файлу
Обработано в main: java.io.IOException: Нет доступа к файлу Правило работает, только если параметр e не переприсваивается в catch (effectively final). Добавьте строку e = new Exception(); — и получите error: unreported exception Exception; must be caught or declared to be thrown.
Оператор throws в сигнатуре метода и checked-исключения
Ключевое слово throws пишется в объявлении метода после списка параметров и перечисляет исключения, которые метод не обрабатывает сам. Для проверяемых (checked) исключений оно обязательно: если метод может выбросить такое исключение и не перехватывает его, без throws возникнет ошибка компиляции. Для Error, RuntimeException и их подклассов throws не требуется.
Смысл правила: если метод способен вызвать исключение, которое сам не обрабатывает, вызывающий код должен об этом знать, чтобы обезопасить себя — перехватить исключение или передать его дальше. Какие классы относятся к проверяемым, разобрано в уроке Иерархия исключений: checked и unchecked.
Пример использования оператора throws:
public class ThrowsGenerateExample {
public static void throwOne() throws IllegalAccessException {
System.out.println("Внутри throwOne.");
throw new IllegalAccessException("demo");
}
public static void main(String[] args) {
try {
throwOne();
} catch (IllegalAccessException e) {
System.out.println("Выброс " + e);
}
}
} Вывод:
Внутри throwOne.
Выброс java.lang.IllegalAccessException: demo IllegalAccessException наследует Exception напрямую, то есть это проверяемое исключение. Если убрать throws IllegalAccessException из сигнатуры throwOne(), компилятор остановится на строке с throw:
ThrowsGenerateExample.java:4: error: unreported exception IllegalAccessException; must be caught or declared to be thrown
throw new IllegalAccessException("demo");
^ Такая же ошибка появится в main, если убрать try-catch вокруг вызова throwOne(): вызывающий метод обязан либо перехватить объявленное исключение, либо сам объявить его в throws — например, public static void main(String[] args) throws IllegalAccessException.
Непроверяемые исключения тоже можно указать в throws — это не ошибка, а документация для читателя кода; компилятор от вызывающего метода их обработки не потребует. Объявлять throws могут и конструкторы; что разрешено при переопределении методов и в конструкторах подкласса, рассказано в уроке Исключения при наследовании.
Распространённое заблуждение
throws не обрабатывает исключение, а только перекладывает ответственность на вызывающий код. Если исключение дойдёт до main, объявленного с throws Exception, программа всё равно завершится с выводом стека вызовов — так же, как без всякой обработки.
Несколько исключений в throws
В throws можно перечислить любое количество типов исключений через запятую, например void load() throws IOException, ParseException. Порядок перечисления не важен. Вызывающий код должен обработать каждое проверяемое исключение из списка: отдельными блоками catch, одним multi-catch или общим суперклассом.
import java.io.IOException;
import java.text.ParseException;
public class MultipleThrowsExample {
static void load(int mode) throws IOException, ParseException {
if (mode == 1) {
throw new IOException("Файл недоступен");
}
if (mode == 2) {
throw new ParseException("Неверная дата", 0);
}
System.out.println("Загружено");
}
public static void main(String[] args) {
for (int mode = 0; mode <= 2; mode++) {
try {
load(mode);
} catch (IOException | ParseException e) {
System.out.println("Ошибка: " + e);
}
}
}
} Вывод:
Загружено
Ошибка: java.io.IOException: Файл недоступен
Ошибка: java.text.ParseException: Неверная дата Нюансы списка throws:
throws Exceptionпокрывает все проверяемые исключения сразу, но скрывает от вызывающего кода, что именно может пойти не так. Перечисляйте конкретные типы.- Подкласс и суперкласс вместе (
throws FileNotFoundException, IOException) — допустимо, хотя избыточно. В multi-catch такое сочетание, наоборот, ошибка компиляции: см. Multi-catch: несколько исключений в одном catch.
Что можно и нельзя бросать через throw
После throw допускается только выражение типа Throwable или его подкласса. Примитивные типы вроде int или char, строки и любые другие классы, кроме Throwable и его наследников, использовать для выброса исключений нельзя — это ошибка компиляции. Если же ссылка подходящего типа равна null, код скомпилируется, но при выполнении будет выброшен NullPointerException.
| Код | Компилируется? | Что произойдёт |
|---|---|---|
throw new IllegalArgumentException("..."); | Да | Непроверяемое исключение, throws не нужен |
throw new IOException("..."); | Только с catch или throws IOException | Иначе unreported exception IOException |
throw e; (параметр catch) | Да | Повторный выброс того же объекта |
throw "Ошибка"; | Нет | incompatible types: String cannot be converted to Throwable |
throw 42; | Нет | incompatible types: int cannot be converted to Throwable |
throw null; или ссылка, равная null | Да | При выполнении — NullPointerException вместо задуманного исключения |
Что выведет этот код?
public class ThrowNullExample {
public static void main(String[] args) {
RuntimeException ex = null;
try {
throw ex;
} catch (NullPointerException e) {
System.out.println("Поймано: " + e);
}
}
} Вывод в Java 15 и новее (подробные сообщения NPE включены по умолчанию):
Поймано: java.lang.NullPointerException: Cannot throw exception because "<local1>" is null Если класс скомпилирован с ключом -g, вместо <local1> будет имя переменной ex. В Java 8 сообщение пустое: Поймано: java.lang.NullPointerException. Блок catch (RuntimeException e) здесь тоже сработал бы: NullPointerException — его подкласс.
Код после throw недостижим
Инструкция throw всегда завершает текущий блок, поэтому инструкция, стоящая сразу после неё в том же блоке, недостижима, и компилятор выдаёт ошибку unreachable statement. По той же причине метод с возвращаемым значением может заканчиваться на throw без return.
static void stop() {
throw new IllegalStateException("Стоп");
System.out.println("Никогда не выполнится"); // error: unreachable statement
} Если throw стоит внутри if, код после if остаётся достижимым: компилятор не знает заранее, выполнится ли условие. А вот так завершать метод можно — ошибки missing return statement не будет:
static int parsePositive(String s) {
int n = Integer.parseInt(s);
if (n > 0) {
return n;
}
throw new IllegalArgumentException("Ожидалось положительное число: " + s);
} Вызов parsePositive("7") вернёт 7, parsePositive("-3") выбросит IllegalArgumentException: Ожидалось положительное число: -3, а parsePositive("abc") — NumberFormatException из Integer.parseInt().
Часто задаваемые вопросы
Можно ли использовать throw без throws?
Да, в двух случаях: если выбрасывается непроверяемое исключение (RuntimeException, Error и их подклассы) или если проверяемое исключение перехватывается блоком catch в том же методе. Если checked-исключение выходит за пределы метода, throws обязателен. Правило с примером — в разделе Оператор throws в сигнатуре метода.
Можно ли объявить в throws исключение, которое метод на самом деле не выбрасывает?
Да, для throws компилятор этого не проверяет: void run() throws IOException {} компилируется. Но тогда каждый вызывающий метод обязан обработать или объявить IOException. С catch наоборот: catch для проверяемого исключения, которое блок try выбросить не может, — ошибка компиляции.
Что означает throws Exception у метода main?
Метод main передаёт проверяемые исключения JVM, не обрабатывая их. Если такое исключение произойдёт, JVM напечатает стек вызовов и завершит поток main. В учебных примерах это удобно, в рабочем коде лучше обрабатывать исключения или объявлять конкретные типы.
Чем throws отличается от тега @throws в Javadoc?
throws — часть кода, его проверяет компилятор. @throws (синоним @exception) — тег документации в комментарии /** ... */: он описывает, в каких случаях метод выбрасывает исключение, и часто используется для непроверяемых исключений, например @throws IllegalArgumentException если amount отрицательный. Компилятор этот тег не проверяет.
Почему в лямбде внутри forEach нельзя выбросить IOException?
Метод accept функционального интерфейса Consumer не объявляет throws, поэтому проверяемое исключение не может выйти из тела лямбды — компилятор сообщит unreported exception IOException. Его перехватывают внутри лямбды и при необходимости оборачивают в UncheckedIOException или другое непроверяемое исключение.
Practice
Reinforce this lesson with hands-on tasks, each with a level and a time estimate.
All module tasks →
Comments