Рубрики
Без рубрики

Java обеспечивает надежную структуру для обработки исключений. Узнайте об обработке исключений в java с примерами программ и рекомендациями.

Java обеспечивает надежную структуру для обработки исключений. Узнайте об обработке исключений в java с примерами программ и рекомендациями.

Автор оригинала: Pankaj Kumar.

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

Зачем нам нужна обработка исключений?

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

Как Java Обрабатывает Исключения?

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

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

Как только среда выполнения получает объект исключения, она пытается найти обработчик для исключения. Обработчик исключений – это блок кода, который может обрабатывать объект исключения.

Логика поиска обработчика исключений проста – запуск поиска в методе, в котором произошла ошибка, если соответствующий обработчик не найден, затем перейдите к вызывающему методу и так далее. Поэтому, если стек вызовов методов A->B->C и в методе C возникает исключение, то поиск соответствующего обработчика будет перенесен из C->B->A .

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

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

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

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

  1. throw – Мы знаем, что при возникновении ошибки создается объект исключения, а затем среда выполнения Java начинает обработку для их обработки. Иногда нам может потребоваться явно генерировать исключения в нашем коде, например, в программе аутентификации пользователей мы должны выдавать исключения клиентам, если пароль равен нулю. Ключевое слово throw используется для создания исключений во время выполнения для его обработки.
  2. throws – Когда мы создаем исключение в методе и не обрабатываем его, мы должны использовать ключевое слово throws в подписи метода, чтобы сообщить вызывающей программе об исключениях, которые могут быть вызваны методом. Вызывающий метод может обрабатывать эти исключения или распространять их на вызывающий метод, используя ключевое слово throws. Мы можем предоставить несколько исключений в предложении throws, и его также можно использовать с методом main ().
  3. try-catch – Мы используем блок try-catch для обработки исключений в нашем коде. try-это начало блока, а catch-в конце блока try для обработки исключений. У нас может быть несколько блоков catch с блоком try. Блок try-catch также может быть вложенным. Блок catch требует параметра, который должен иметь тип Exception.
  4. наконец – блок 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");
		}
	}
}

Вывод :


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)

Метод testException() создает исключения с использованием ключевого слова throw. Подпись метода использует ключевое слово throws, чтобы сообщить вызывающему абоненту тип исключений, которые он может создать.

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

Исключение testException(-10) никогда не выполняется из-за исключения, а затем выполняется блок finally. Функция printStackTrace() является одним из полезных методов в классе исключений для целей отладки.

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

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

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

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

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

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

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

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

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

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

Если вы улавливаете много исключений в одном блоке 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 7 .

Как создать пользовательский класс исключений в Java?

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

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

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;
	}
}
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");
			}
		}
	}
}

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

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

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

Рекомендации по обработке исключений в Java

  • Используйте конкретные исключения – Базовые классы иерархии исключений не предоставляют никакой полезной информации, поэтому в Java так много классов исключений, таких как исключение IOException с дополнительными подклассами, такими как исключение FileNotFoundException, исключение EOFException и т. Д. Мы всегда должны создавать и перехватывать определенные классы исключений, чтобы вызывающий абонент мог легко узнать первопричину исключения и обработать их. Это упрощает отладку и помогает клиентским приложениям надлежащим образом обрабатывать исключения.
  • Выбрасывать рано или быстро – Мы должны стараться выбрасывать исключения как можно раньше. Рассмотрим приведенный выше метод process File (), если мы передадим этому методу аргумент null, мы получим следующее исключение.
Exception in thread "main" java.lang.NullPointerException
	at java.io.FileInputStream.(FileInputStream.java:134)
	at java.io.FileInputStream.(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
}

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

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 для обработки нескольких исключений в одном блоке перехвата. Такой подход уменьшит размер нашего кода, и он тоже будет выглядеть чище.
  • Использование пользовательских исключений – Всегда лучше определить стратегию обработки исключений во время разработки, и вместо того, чтобы создавать и перехватывать несколько исключений, мы можем создать пользовательское исключение с кодом ошибки, и вызывающая программа может обрабатывать эти коды ошибок. Также неплохо создать служебный метод для обработки различных кодов ошибок и их использования.
  • Соглашения об именах и упаковка – При создании пользовательского исключения убедитесь, что оно заканчивается исключением, чтобы из самого имени было ясно, что это класс исключений. Также убедитесь, что они упакованы так, как это сделано в JDK, например, исключение IOException является базовым исключением для всех операций ввода-вывода.
  • Используйте исключения разумно – Исключения являются дорогостоящими, и иногда вообще не требуется создавать исключения, и мы можем вернуть логическую переменную вызывающей программе, чтобы указать, была ли операция успешной или нет. Это полезно в тех случаях, когда операция необязательна, и вы не хотите, чтобы ваша программа застряла из-за сбоя. Например, при обновлении котировок акций в базе данных с помощью стороннего веб-сервиса мы можем захотеть избежать возникновения исключений в случае сбоя подключения.
  • Документируйте исключения, создаваемые – Используйте Javadoc @throws чтобы четко указать исключения, создаваемые методом, это очень полезно, когда вы предоставляете интерфейс для использования другими приложениями.