Привет! Не хочется тебе об этом говорить, но огромная часть работы программиста это работа с ошибками. Причем чаще всего со своими собственными.
Коротко: исключение это ошибка, возникшая во время работы программы. Код, где ошибка возможна, помещают в блок
Код, в котором программист ожидает возникновения исключений, помещается в блок
Теперь мы точно уверены, что позаботились о занятых ресурсах независимо от того, что произойдет при работе программы :)
Это еще не все, что тебе нужно знать об исключениях. Обработка ошибок это очень важная тема в программировании: ей посвящена не одна статья. Базовые принципы работы с исключениями также описаны в документации Oracle (Lesson: Exceptions).
На следующем занятии мы узнаем, какие бывают виды исключений и как создать свое собственное исключение:) До встречи!
try, а обработку в catch. Блок finally выполняется всегда (в нем освобождают ресурсы). Обработать исключение можно двумя способами: на месте через try-catch или пробросить наверх через throws. Современный способ работы с ресурсами это try-with-resources.
Так уж сложилось, что не бывает людей, которые не допускают ошибок. И программ таких тоже не бывает. Конечно, главное при работе над ошибкой это понять ее причину. А причин таких в программе может быть целая куча.
В один прекрасный момент перед создателями Java встал вопрос: что делать с этими самыми потенциальными ошибками в программах? Избежать их полностью нереально. Программисты могут понаписать такого, что невозможно даже представить :)
Значит, надо заложить в язык механизм работы с ошибками. Иными словами, если уж в программе произошла какая-то ошибка, нужен сценарий для дальнейшей работы. Что именно программа должна делать при возникновении ошибки?
Сегодня мы познакомимся с этим механизмом. И называется он "Исключения" (Exceptions).
Кратко
- Исключение это незапланированная ошибка во время работы программы; каждое представлено своим классом (все наследуются от
Throwable). - Код, где возможна ошибка, помещают в блок
try, а обработку в блокcatch(для конкретного типа исключения). - Блоков
catchможет быть несколько, по одному на каждый тип исключения. - Два способа обработки: обработать самому в
try-catchили пробросить наверх черезthrows. - Блок
finallyвыполняется всегда: и при ошибке, и без нее; в нем освобождают ресурсы. - Современный способ:
try-with-resourcesзакрывает ресурсы автоматически.
Что такое исключение в Java
Исключение это некая исключительная, незапланированная ситуация, которая произошла при работе программы. Примеров исключений в Java может быть много. Например, ты написал код, который считывает текст из файла и выводит в консоль первую строку.public class Main {
public static void main(String[] args) throws IOException {
BufferedReader reader = new BufferedReader(new FileReader("C:\\Users\\Username\\Desktop\\test.txt"));
String firstString = reader.readLine();
System.out.println(firstString);
}
}
Но такого файла не существует!
Результатом работы программы будет исключение, FileNotFoundException. Кстати, что такое BufferedReader и как считывать данные, мы разбирали в статье про считывание с клавиатуры (ридеры).
Вывод:
Exception in thread "main" java.io.FileNotFoundException: C:\Users\Username\Desktop\test.txt (Системе не удается найти указанный путь)
Каждое исключение представлено в Java отдельным классом.
Все классы исключений происходят от общего "предка", родительского класса Throwable.
Название класса-исключения обычно коротко отображает причину его возникновения:
FileNotFoundException(файл не найден)ArithmeticException(исключение при выполнении математической операции)ArrayIndexOutOfBoundsException(указан номер ячейки массива за пределами его длины). Например, если попытаться вывести в консоль ячейку array[23] для массива array длиной 10.
Exception in thread "main"
Э-э-э-м :/ Ничего не понятно. Что за ошибка, откуда взялась, неясно. Никакой полезной информации нет.
А вот благодаря такому разнообразию классов программист получает для себя главное: тип ошибки и ее вероятную причину, которая заложена в названии класса.
Ведь совсем другое дело увидеть в консоли:
Exception in thread "main" java.io.FileNotFoundException: C:\Users\Username\Desktop\test.txt (Системе не удается найти указанный путь)
Сразу становится понятно, в чем может быть дело и "в какую сторону копать" для решения проблемы!
Исключения, как и любые экземпляры классов, являются объектами.
Перехват и обработка исключений
Для работы с исключениями в Java существуют специальные блоки кода:try, catch и finally.
try. Это не значит, что исключение в этом месте обязательно произойдет. Это значит, что оно может там произойти, и программист в курсе этого.
Тип ошибки, который ты ожидаешь получить, помещается в блок catch ("перехват"). Сюда же помещается весь код, который нужно выполнить, если исключение произойдет.
Вот пример:
public static void main(String[] args) throws IOException {
try {
BufferedReader reader = new BufferedReader(new FileReader("C:\\Users\\Username\\Desktop\\test.txt"));
String firstString = reader.readLine();
System.out.println(firstString);
} catch (FileNotFoundException e) {
System.out.println("Ошибка! Файл не найден!");
}
}
Вывод:
Ошибка! Файл не найден!
Мы поместили наш код в два блока. В первом блоке мы ожидаем, что может произойти ошибка "Файл не найден". Это блок try.
Во втором указываем программе что делать, если произошла ошибка. Причем ошибка конкретного вида, FileNotFoundException. Если мы передадим в скобки блока catch другой класс исключения, оно не будет перехвачено.
public static void main(String[] args) throws IOException {
try {
BufferedReader reader = new BufferedReader(new FileReader("C:\\Users\\Username\\Desktop\\test.txt"));
String firstString = reader.readLine();
System.out.println(firstString);
} catch (ArithmeticException e) {
System.out.println("Ошибка! Файл не найден!");
}
}
Вывод:
Exception in thread "main" java.io.FileNotFoundException: C:\Users\Username\Desktop\test.txt (Системе не удается найти указанный путь)
Код в блоке catch не отработал, потому что мы "настроили" этот блок на перехват ArithmeticException, а код в блоке try выбросил другой тип, FileNotFoundException. Для FileNotFoundException мы не написали сценарий, поэтому программа вывела в консоль ту информацию, которая выводится по умолчанию для FileNotFoundException.
Здесь тебе нужно обратить внимание на 3 вещи.Три важные особенности try-catch
Первое. Как только в какой-то строчке кода в блоке try возникнет исключение, код после нее уже не будет выполнен. Выполнение программы сразу "перепрыгнет" в блокcatch.
Например:
public static void main(String[] args) {
try {
System.out.println("Делим число на ноль");
System.out.println(366/0);//в этой строчке кода будет выброшено исключение
System.out.println("Этот");
System.out.println("код");
System.out.println("не");
System.out.println("будет");
System.out.println("выполнен!");
} catch (ArithmeticException e) {
System.out.println("Программа перепрыгнула в блок catch!");
System.out.println("Ошибка! Нельзя делить на ноль!");
}
}
Вывод:
Делим число на ноль
Программа перепрыгнула в блок catch!
Ошибка! Нельзя делить на ноль!
В блоке try во второй строчке мы попытались разделить число на 0, в результате чего возникло исключение ArithmeticException.
После этого строки 6-10 блока try выполнены уже не будут. Как мы и говорили, программа сразу начала выполнять блок catch.
Второе.
Блоков catch может быть несколько.
Если код в блоке try может выбросить не один, а несколько видов исключений, для каждого из них можно написать свой блок catch.
public static void main(String[] args) throws IOException {
try {
BufferedReader reader = new BufferedReader(new FileReader("C:\\Users\\Username\\Desktop\\test.txt"));
System.out.println(366/0);
String firstString = reader.readLine();
System.out.println(firstString);
} catch (FileNotFoundException e) {
System.out.println("Ошибка! Файл не найден!");
} catch (ArithmeticException e) {
System.out.println("Ошибка! Деление на 0!");
}
}
В этом примере мы написали два блока catch. Если в блоке try произойдет FileNotFoundException, будет выполнен первый блок catch.
Если произойдет ArithmeticException, выполнится второй.
Блоков catch ты можешь написать хоть 50. Но, конечно, лучше не писать код, который может выбросить 50 разных видов ошибок :)
Третье.
Откуда тебе знать, какие исключения может выбросить твой код?
Ну, про некоторые ты, конечно, можешь догадываться, но держать все в голове невозможно. Поэтому компилятор Java знает о самых распространенных исключениях и знает, в каких ситуациях они могут возникнуть.
Например, если ты написал код и компилятор знает, что при его работе могут возникнуть 2 вида исключений, твой код не скомпилируется, пока ты их не обработаешь. Примеры этого мы увидим ниже.
Теперь что касается обработки исключений. Существует 2 способа их обработки:
- Обработать исключение на месте, в блоке
try-catch. - Пробросить исключение наверх по стеку вызовов через
throws, тогда обработку выполнит вызывающий метод.
catch().
Есть и второй вариант: метод может выбросить исключение вверх по стеку вызовов.
Что это значит?
Например, у нас в классе есть метод, все тот же printFirstString(), который считывает файл и выводит в консоль его первую строку:
public static void printFirstString(String filePath) {
BufferedReader reader = new BufferedReader(new FileReader(filePath));
String firstString = reader.readLine();
System.out.println(firstString);
}
На текущий момент наш код не компилируется, потому что в нем есть необработанные исключения.
В строке 1 ты указываешь путь к файлу. Компилятор знает, что такой код легко может привести к FileNotFoundException.
В строке 3 ты считываешь текст из файла. В этом процессе легко может возникнуть IOException, ошибка при вводе-выводе данных (Input-Output).
Сейчас компилятор говорит тебе: "Чувак, я не одобрю этот код и не скомпилирую его, пока ты не скажешь мне, что я должен делать в случае, если произойдет одно из этих исключений. А они точно могут произойти, исходя из того кода, который ты написал!".
Деваться некуда, нужно обрабатывать оба!
Первый вариант обработки нам уже знаком: надо поместить наш код в блок try, и добавить два блока catch:
public static void printFirstString(String filePath) {
try {
BufferedReader reader = new BufferedReader(new FileReader(filePath));
String firstString = reader.readLine();
System.out.println(firstString);
} catch (FileNotFoundException e) {
System.out.println("Ошибка, файл не найден!");
e.printStackTrace();
} catch (IOException e) {
System.out.println("Ошибка при вводе/выводе данных из файла!");
e.printStackTrace();
}
}
Но это не единственный вариант.
Мы можем не писать сценарий для ошибки внутри метода, и просто пробросить исключение наверх.Как обрабатывать исключения: два способа
Проброс делается с помощью ключевого словаthrows, которое пишется в объявлении метода:
public static void printFirstString(String filePath) throws FileNotFoundException, IOException {
BufferedReader reader = new BufferedReader(new FileReader(filePath));
String firstString = reader.readLine();
System.out.println(firstString);
}
После слова throws мы через запятую перечисляем все виды исключений, которые этот метод может выбросить при работе.
Зачем это делается?
Теперь, если кто-то в программе захочет вызвать метод printFirstString(), он должен будет сам реализовать обработку исключений. К примеру, в другой части программы кто-то из твоих коллег написал метод, внутри которого вызывает твой метод printFirstString():
public static void yourColleagueMethod() {
//...метод твоего коллеги что-то делает
//...и в один момент вызывает твой метод printFirstString() c нужным ему файлом
printFirstString("C:\\Users\\Евгений\\Desktop\\testFile.txt");
}
Ошибка, код не компилируется!
В методе printFirstString() мы не написали сценарий обработки ошибок. Поэтому задача ложится на плечи тех, кто будет этот метод использовать.
То есть перед методом yourColleagueMethod() теперь стоят те же 2 варианта: он должен или обработать оба исключения, которые ему "прилетели", с помощью try-catch, или пробросить их дальше.
public static void yourColleagueMethod() throws FileNotFoundException, IOException {
//...метод что-то делает
//...и в один момент вызывает твой метод printFirstString() c нужным ему файлом
printFirstString("C:\\Users\\Евгений\\Desktop\\testFile.txt");
}
Во втором случае обработка ляжет на плечи следующего по стэку метода, того, который будет вызывать yourColleagueMethod().
Вот поэтому такой механизм называется "пробрасыванием исключения наверх", или "передачей наверх".
Когда ты пробрасываешь исключения наверх с помощью throws, код компилируется. Компилятор в этот момент как бы говорит: "Окей, ладно. Твой код содержит кучу потенциальных исключений, но я, так и быть, его скомпилирую. Мы еще вернемся к этому разговору!"
И когда ты где-то в программе вызываешь метод, который не обработал свои исключения, компилятор выполняет свое обещание и снова напоминает о них.Блок finally: выполняется всегда
В завершении мы поговорим о блокеfinally (простите за каламбур). Это последняя часть триумвирата обработки исключений try-catch-finally.
Его особенность в том, что он выполняется при любом сценарии работы программы.
public static void main(String[] args) throws IOException {
try {
BufferedReader reader = new BufferedReader(new FileReader("C:\\Users\\Username\\Desktop\\test.txt"));
String firstString = reader.readLine();
System.out.println(firstString);
} catch (FileNotFoundException e) {
System.out.println("Ошибка! Файл не найден!");
e.printStackTrace();
} finally {
System.out.println("А вот и блок finally!");
}
}
В этом примере код внутри блока finally выполняется в обоих случаях.
Если код в блоке try выполнится целиком и не выбросит исключения, в конце сработает блок finally.
Если код внутри try прервется, и программа перепрыгнет в блок catch, после того, как отработает код внутри catch, все равно будет выбран блок finally.
Зачем он нужен?
Его главное назначение это выполнить обязательную часть кода; ту часть, которая должна быть выполнена независимо от обстоятельств.
Например, в нем часто освобождают какие-то используемые программой ресурсы.
В нашем коде мы открываем поток для чтения информации из файла и передаем его в объект BufferedReader.
Наш reader нужно закрыть и освободить ресурсы. Это нужно сделать в любом случае: неважно, отработает программа как надо или вызовет исключение.
Это удобно делать в блоке finally:
public static void main(String[] args) throws IOException {
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader("C:\\Users\\Username\\Desktop\\test.txt"));
String firstString = reader.readLine();
System.out.println(firstString);
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
System.out.println("А вот и блок finally!");
if (reader != null) {
reader.close();
}
}
}Современный подход с try-with-resources
С Java 7+ появилась конструкция try-with-resources, которая автоматически закрывает ресурсы. Это более чистый и безопасный способ. Здесь используется класс Scanner, который мы разбирали отдельно:import java.util.Scanner;
public int readNumber() throws InvalidInputException {
// Scanner автоматически закроется после выхода из блока try
try (Scanner scanner = new Scanner(System.in)) {
System.out.print("Введите число: ");
int number = scanner.nextInt();
if (number < 0) {
throw new InvalidInputException("Число не может быть отрицательным!");
}
return number;
} catch (InputMismatchException e) {
System.out.println("Ошибка: введено не число!");
throw new InvalidInputException("Некорректный ввод данных");
}
}Итог
Сведем три блока обработки исключений в одну таблицу:| Блок | Зачем нужен | Когда выполняется |
|---|---|---|
try | код, в котором возможно исключение | начинает выполняться всегда; прерывается на строке, где возникло исключение |
catch | обработка исключения конкретного типа | только если в try возникло соответствующее исключение |
finally | обязательный код, например освобождение ресурсов | всегда: и при ошибке, и без нее |
Что еще почитать |
|---|
Типы исключений: checked, unchecked и свои собственные (Средний уровень - углубление) Как обрабатывать исключения в Java (Продвинутый уровень) |
Вопросы и ответы
Что такое исключение в Java?
Это незапланированная ошибка, возникшая во время работы программы (например, файл не найден или деление на ноль). Каждое исключение представлено отдельным классом, все они наследуются отThrowable.
Для чего нужны блоки try и catch?
Вtry помещают код, где может возникнуть исключение, а в catch код, который выполнится, если исключение произойдет. catch перехватывает исключение конкретного типа.
Что делает блок finally?
Он выполняется всегда: и если исключение произошло, и если нет. В нем размещают обязательный код, например освобождение ресурсов (закрытие файла или потока).Чем throws отличается от try-catch?
try-catch обрабатывает исключение на месте, а throws пробрасывает его наверх по стеку вызовов: тогда обрабатывать исключение должен тот, кто вызвал метод.
Можно ли написать несколько блоков catch?
Да. Если код вtry может выбросить разные типы исключений, для каждого пишут свой блок catch. Выполнится тот, который соответствует возникшему исключению.
Что такое try-with-resources?
Это современная конструкция (с Java 7), в которой ресурс объявляется в скобках послеtry и закрывается автоматически при выходе из блока. Это чище и безопаснее, чем закрывать ресурс вручную в finally.