Поиск по сайту:

Обработка исключений в Java


Введение

Исключение – это событие ошибки, которое может произойти во время выполнения программы и нарушить ее нормальный ход. Java предоставляет надежный и объектно-ориентированный способ обработки сценариев исключений, известный как обработка исключений Java.

Исключения в Java могут возникать из-за различных ситуаций, таких как неверные данные, введенные пользователем, аппаратный сбой, сбой сетевого подключения или сервер базы данных, который не работает. Код, который указывает, что делать в определенных сценариях исключений, называется обработкой исключений.

Генерация и перехват исключений

Java создает объект-исключение при возникновении ошибки при выполнении оператора. Объект исключения содержит много отладочной информации, такой как иерархия методов, номер строки, в которой произошло исключение, и тип исключения.

Если в методе возникает исключение, процесс создания объекта исключения и передачи его в среду выполнения называется \созданием исключения. Нормальный поток программы останавливается, и среда выполнения Java Среда (JRE) пытается найти обработчик исключения.Обработчик исключений — это блок кода, который может обрабатывать объект исключения.

  • Логика поиска обработчика исключений начинается с поиска в методе, в котором произошла ошибка.
  • Если подходящий обработчик не найден, он переходит к вызывающему методу.
  • И так далее.

Таким образом, если стек вызовов метода — A->B->C, а в методе C возникает исключение, то поиск соответствующего обработчика переместится из С->Б->А.

Если соответствующий обработчик исключений найден, объект исключения передается обработчику для его обработки. Говорят, что обработчик \перехватывает исключение. Если подходящий обработчик исключения не найден, программа завершает работу и выводит информацию об исключении на консоль.

Платформа обработки исключений Java используется только для обработки ошибок времени выполнения. Ошибки времени компиляции должны быть исправлены разработчиком, написавшим код, иначе программа не будет выполняться.

Ключевые слова обработки исключений Java

Java предоставляет специальные ключевые слова для целей обработки исключений.

  1. throw — мы знаем, что при возникновении ошибки создается объект исключения, а затем среда выполнения Java начинает обработку для их обработки. Иногда нам может понадобиться явно генерировать исключения в нашем коде. Например, в программе аутентификации пользователей мы должны генерировать исключения для клиентов, если пароль равен null. Ключевое слово throw используется для создания исключений в среде выполнения для их обработки.
  2. throws — когда мы выбрасываем исключение в методе, а не обрабатываем его, мы должны использовать ключевое слово throws в сигнатуре метода, чтобы сообщить вызывающей программе об исключениях, которые могут быть выброшены. по методу. Вызывающий метод может обрабатывать эти исключения или передавать их своему вызывающему методу с помощью ключевого слова throws. Мы можем предоставить несколько исключений в предложении throws, и его также можно использовать с методом main().
  3. try-catch — мы используем блок try-catch для обработки исключений в нашем коде. try — это начало блока, а catch — конец блока try для обработки исключений. У нас может быть несколько блоков catch с блоком try. Блок try-catch также может быть вложенным. Для блока catch требуется параметр, который должен иметь тип Exception.
  4. finally — блок finally является необязательным и может использоваться только с блоком try-catch. Поскольку исключение останавливает процесс выполнения, у нас могут быть открыты некоторые ресурсы, которые не будут закрыты, поэтому мы можем использовать блок finally. Блок finally всегда выполняется независимо от того, произошло ли исключение или нет.

Пример обработки исключений

package com.journaldev.exceptions;

import java.io.FileNotFoundException;
import java.io.IOException;

public class ExceptionHandling {

	public static void main(String[] args) throws FileNotFoundException, IOException {
		try {
			testException(-5);
			testException(-10);
		} catch(FileNotFoundException e) {
			e.printStackTrace();
		} catch(IOException e) {
			e.printStackTrace();
		} finally {
			System.out.println("Releasing resources");
		}
		testException(15);
	}

	public static void testException(int i) throws FileNotFoundException, IOException {
		if (i < 0) {
			FileNotFoundException myException = new FileNotFoundException("Negative Integer " + i);
			throw myException;
		} else if (i > 10) {
			throw new IOException("Only supported for index 0 to 10");
		}
	}
}

  • Метод testException() генерирует исключения с помощью ключевого слова throw. В сигнатуре метода используется ключевое слово throws, чтобы вызывающая сторона знала, какие исключения она может генерировать.
  • В методе main() я обрабатываю исключения с помощью блока try-catch в методе main(). Когда я не обрабатываю его, я распространяю его во время выполнения с помощью предложения throws в методе main().
  • testException(-10) никогда не выполняется из-за исключения, а затем выполняется блок finally.

printStackTrace() — один из полезных методов класса Exception для отладки.

Этот код выведет следующее:

Output
java.io.FileNotFoundException: Negative Integer -5 at com.journaldev.exceptions.ExceptionHandling.testException(ExceptionHandling.java:24) at com.journaldev.exceptions.ExceptionHandling.main(ExceptionHandling.java:10) Releasing resources Exception in thread "main" java.io.IOException: Only supported for index 0 to 10 at com.journaldev.exceptions.ExceptionHandling.testException(ExceptionHandling.java:27) at com.journaldev.exceptions.ExceptionHandling.main(ExceptionHandling.java:19)

Некоторые важные моменты, на которые следует обратить внимание:

  • У нас не может быть предложения catch или finally без оператора try.
  • У оператора try должен быть либо блок catch, либо блок finally, он может иметь оба блока.
  • Мы не можем писать код между блоками try-catch-finally.
  • У нас может быть несколько блоков catch с одним оператором try.
  • Блоки
  • try-catch могут быть вложены аналогично операторам if-else.
  • У нас может быть только один блок finally с оператором try-catch.

Иерархия исключений Java

Как указывалось ранее, при возникновении исключения создается объект исключения. Исключения Java являются иерархическими, и наследование используется для категоризации различных типов исключений. Throwable — это родительский класс иерархии исключений Java, который имеет два дочерних объекта — Error и Exception. Исключения далее делятся на проверенные исключения и исключения среды выполнения.

  1. Ошибки. Ошибки — это исключительные ситуации, которые выходят за рамки приложения, и их невозможно предвидеть и исправить. Например, сбой оборудования, сбой виртуальной машины Java (JVM) или ошибка нехватки памяти. Вот почему у нас есть отдельная иерархия Error, и мы не должны пытаться обрабатывать эти ситуации. Некоторыми из распространенных ошибок являются OutOfMemoryError и StackOverflowError.
  2. Проверенные исключения: проверенные исключения — это исключительные сценарии, которые мы можем предвидеть в программе и попытаться исправить их. Например, FileNotFoundException. Мы должны поймать это исключение и предоставить полезное сообщение пользователю и правильно зарегистрировать его для целей отладки. Exception является родительским классом для всех Checked Exception. Если мы выбрасываем Checked Exception, мы должны перехватить его в том же методе, или мы должны передать его вызывающей стороне с помощью throws ключевое слово.
  3. Исключение во время выполнения: Исключения во время выполнения вызваны неправильным программированием. Например, попытка получить элемент из массива. Мы должны сначала проверить длину массива, прежде чем пытаться получить элемент, иначе он может вызвать ArrayIndexOutOfBoundException во время выполнения. RuntimeException является родительским классом для всех Exception среды выполнения. Если мы выбрасываем какие-либо Exception времени выполнения в методе, не требуется указывать их в предложении throws сигнатуры метода. Исключений во время выполнения можно избежать, если лучше программировать.

Некоторые полезные методы классов исключений

Java Exception и все его подклассы не предоставляют никаких конкретных методов, и все методы определены в базовом классе — Throwable. Классы Exception созданы для указания различных типов сценариев Exception, чтобы мы могли легко определить основную причину и обработать Exception в соответствии с его типом. . Класс Throwable реализует интерфейс Serializable для взаимодействия.

Некоторые из полезных методов класса Throwable:

  1. public String getMessage() — этот метод возвращает сообщение String из Throwable, и сообщение может быть предоставлено при создании исключения через его конструктор.
  2. public String getLocalizedMessage() — этот метод предоставляется для того, чтобы подклассы могли переопределить его, чтобы предоставить вызывающей программе сообщение, зависящее от локали. Реализация этого метода в классе Throwable использует метод getMessage() для возврата сообщения об исключении.
  3. общедоступный синхронизированный Throwable getCause() — этот метод возвращает причину исключения или null, если причина неизвестна.
  4. public String toString() — этот метод возвращает информацию о Throwable в формате String, возвращаемый String содержит имя Throwable класс и локализованное сообщение.
  5. public void printStackTrace() — этот метод печатает информацию о трассировке стека в стандартный поток ошибок, этот метод перегружен, и мы можем передать PrintStream или PrintWriter в качестве аргумент для записи информации о трассировке стека в файл или поток.

Автоматическое управление ресурсами Java 7 и улучшения блока Catch

Если вы перехватываете много исключений в одном блоке try, вы заметите, что код блока catch в основном состоит из избыточного кода для зарегистрируйте ошибку. В Java 7 одной из функций был улучшенный блок catch, в котором мы можем перехватывать несколько исключений в одном блоке catch. Вот пример блока catch с этой функцией:

catch (IOException | SQLException ex) {
    logger.error(ex);
    throw new MyException(ex.getMessage());
}

Существуют некоторые ограничения, например, объект исключения является окончательным, и мы не можем изменить его внутри блока catch. Полный анализ читайте в разделе Улучшения блока Catch в Java 7.

В большинстве случаев мы используем блок finally просто для закрытия ресурсов. Иногда мы забываем закрыть их и получаем исключения во время выполнения, когда ресурсы исчерпаны. Эти исключения трудно отлаживать, и нам может потребоваться изучить каждое место, где мы используем этот ресурс, чтобы убедиться, что мы его закрываем. В Java 7 одним из улучшений стал try-with-resources, где мы можем создать ресурс в самом операторе try и использовать его внутри try-catch. блок. Когда выполнение выходит из блока try-catch, среда выполнения автоматически закрывает эти ресурсы. Вот пример блока try-catch с этим улучшением:

try (MyResource mr = new MyResource()) {
	System.out.println("MyResource created in try-with-resources");
} catch (Exception e) {
	e.printStackTrace();
}

Пример пользовательского класса исключений

Java предоставляет нам множество классов исключений, но иногда нам может понадобиться создать собственные классы исключений. Например, чтобы уведомить вызывающую сторону об определенном типе исключения с помощью соответствующего сообщения. У нас могут быть настраиваемые поля для отслеживания, например коды ошибок. Например, предположим, что мы пишем метод для обработки только текстовых файлов, поэтому мы можем предоставить вызывающей стороне соответствующий код ошибки, когда в качестве входных данных отправляется файл какого-либо другого типа.

Сначала создайте MyException:

package com.journaldev.exceptions;

public class MyException extends Exception {

	private static final long serialVersionUID = 4664456874499611218L;

	private String errorCode = "Unknown_Exception";

	public MyException(String message, String errorCode) {
		super(message);
		this.errorCode=errorCode;
	}

	public String getErrorCode() {
		return this.errorCode;
	}
}

Затем создайте CustomExceptionExample:

package com.journaldev.exceptions;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;

public class CustomExceptionExample {

	public static void main(String[] args) throws MyException {
		try {
			processFile("file.txt");
		} catch (MyException e) {
			processErrorCodes(e);
		}
	}

	private static void processErrorCodes(MyException e) throws MyException {
		switch (e.getErrorCode()) {
			case "BAD_FILE_TYPE":
				System.out.println("Bad File Type, notify user");
				throw e;
			case "FILE_NOT_FOUND_EXCEPTION":
				System.out.println("File Not Found, notify user");
				throw e;
			case "FILE_CLOSE_EXCEPTION":
				System.out.println("File Close failed, just log it.");
				break;
			default:
				System.out.println("Unknown exception occured, lets log it for further debugging." + e.getMessage());
				e.printStackTrace();
		}
	}

	private static void processFile(String file) throws MyException {
		InputStream fis = null;

		try {
			fis = new FileInputStream(file);
		} catch (FileNotFoundException e) {
			throw new MyException(e.getMessage(), "FILE_NOT_FOUND_EXCEPTION");
		} finally {
			try {
				if (fis != null) fis.close();
			} catch (IOException e) {
				throw new MyException(e.getMessage(), "FILE_CLOSE_EXCEPTION");
			}
		}
	}
}

У нас может быть отдельный метод для обработки разных типов кодов ошибок, которые мы получаем от разных методов. Некоторые из них потребляются, потому что мы можем не захотеть уведомлять пользователя об этом, или некоторые из них мы возвращаем, чтобы уведомить пользователя о проблеме.

Здесь я расширяю Exception, так что всякий раз, когда создается это исключение, оно должно обрабатываться в методе или возвращаться в вызывающую программу. Если мы расширяем RuntimeException, нет необходимости указывать его в предложении throws.

Это было дизайнерское решение. Использование Checked Exception имеет то преимущество, что помогает разработчикам понять, какие исключения вы можете ожидать, и принять соответствующие меры для их обработки.

Лучшие практики обработки исключений в Java

  • Использовать определенные исключения — базовые классы иерархии исключений не предоставляют никакой полезной информации, поэтому в Java так много классов исключений, таких как IOException с дополнительными подклассами, такими как FileNotFoundException, EOFException и т. д. Мы всегда должны throw и catch определенные классы исключений, чтобы вызывающая сторона могла легко узнать основную причину исключения. и обрабатывать их. Это упрощает отладку и помогает клиентским приложениям правильно обрабатывать исключения.
  • Раннее выбрасывание или отказоустойчивость. Мы должны стараться генерировать исключения как можно раньше. Рассмотрим приведенный выше метод processFile(). Если мы передадим аргумент null этому методу, мы получим следующее исключение:

Output
Exception in thread "main" java.lang.NullPointerException at java.io.FileInputStream.<init>(FileInputStream.java:134) at java.io.FileInputStream.<init>(FileInputStream.java:97) at com.journaldev.exceptions.CustomExceptionExample.processFile(CustomExceptionExample.java:42) at com.journaldev.exceptions.CustomExceptionExample.main(CustomExceptionExample.java:12)

Во время отладки нам придется внимательно следить за трассировкой стека, чтобы определить фактическое местоположение исключения. Если мы изменим нашу логику реализации, чтобы проверить эти исключения раньше, как показано ниже:

private static void processFile(String file) throws MyException {
	if (file == null) throw new MyException("File name can't be null", "NULL_FILE_NAME");

	// ... further processing
}

Затем трассировка стека исключений будет указывать, где произошло исключение, с четким сообщением:

Output
com.journaldev.exceptions.MyException: File name can't be null at com.journaldev.exceptions.CustomExceptionExample.processFile(CustomExceptionExample.java:37) at com.journaldev.exceptions.CustomExceptionExample.main(CustomExceptionExample.java:12)

  • Поймать позже — поскольку Java требует либо обработки проверенного исключения, либо объявления его в сигнатуре метода, иногда разработчики склонны перехватывать исключение и регистрировать ошибку. Но такая практика вредна, потому что вызывающая программа не получает никакого уведомления об исключении. Мы должны перехватывать исключения только тогда, когда мы можем правильно их обработать. Например, в приведенном выше методе я отбрасываю исключения обратно в вызывающий метод для их обработки. Тот же метод может быть использован другими приложениями, которые могут захотеть обработать исключение другим способом. При реализации любой функции мы всегда должны отбрасывать исключения обратно вызывающей стороне и позволять им решать, как с ними обращаться.
  • Закрытие ресурсов. Поскольку исключения останавливают обработку программы, мы должны закрыть все ресурсы в блоке finally или использовать расширение Java 7 try-with-resources, чтобы позволить среде выполнения Java закрыть их для вас.
  • Регистрация исключений. Мы всегда должны регистрировать сообщения об исключениях, а при генерировании исключений предоставлять четкое сообщение, чтобы вызывающая сторона могла легко понять, почему возникло исключение. Мы всегда должны избегать пустого блока catch, который просто использует исключение и не предоставляет каких-либо значимых сведений об исключении для отладки.
  • Один блок перехвата для нескольких исключений. В большинстве случаев мы регистрируем сведения об исключении и предоставляем сообщение пользователю, в этом случае мы должны использовать функцию Java 7 для обработки нескольких исключений в одном catch блокировать. Такой подход уменьшит размер нашего кода, и он также будет выглядеть чище.
  • Использование пользовательских исключений. Всегда лучше определить стратегию обработки исключений во время разработки, и вместо того, чтобы выбрасывать несколько исключений и перехватывать несколько исключений, мы можем создать пользовательский исключение с кодом ошибки, и вызывающая программа может обрабатывать эти коды ошибок. Также рекомендуется создать служебный метод для обработки различных кодов ошибок и их использования.
  • Соглашения об именах и упаковках. При создании пользовательского исключения убедитесь, что оно заканчивается на Exception, чтобы из самого имени было ясно, что это класс исключения. Кроме того, не забудьте упаковать их, как это сделано в Java Development Kit (JDK). Например, IOException является базовым исключением для всех операций ввода-вывода.
  • Разумное использование исключений. Исключения обходятся дорого, а иногда вообще не требуется генерировать исключения, и мы можем вернуть вызывающей программе логическую переменную, чтобы указать, была ли операция успешной или нет. Это полезно, когда операция необязательна, и вы не хотите, чтобы ваша программа зависала из-за сбоя. Например, при обновлении котировок акций в базе данных из стороннего веб-сервиса мы можем захотеть избежать создания исключений в случае сбоя подключения.
  • Документируйте сгенерированные исключения — используйте Javadoc @throws, чтобы четко указать исключения, сгенерированные методом. Это очень полезно, когда вы предоставляете интерфейс для использования другими приложениями.

Заключение

В этой статье вы узнали об обработке исключений в Java. Вы узнали о throw и throws. Вы также узнали о блоках trytry-with-resources), catch и finally.