Иерархия исключений java. Введение в обработку исключений

Привет, Хабр! Представляю вашему вниманию перевод статьи Fixing 7 Common Java Exception Handling Mistakes автора Thorben Janssen.

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

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

Ошибка 1: объявление java.lang.Exception или java.lang.Throwable

Как вы уже знаете, вам нужно либо объявить, либо обработать проверяемое исключение. Но проверяемые исключения - это не единственные, которые вы можете указать. Вы можете использовать любой подкласс java.lang.Throwable в предложении throws. Таким образом, вместо указания двух разных исключений, которые выбрасывает следующий фрагмент кода, вы можете просто использовать исключение java.lang.Exception в предложении throws.

Public void doNotSpecifyException() throws Exception { doSomething(); } public void doSomething() throws NumberFormatException, IllegalArgumentException { // do something }
Но это не значит, что вы должны это сделать. Указание Exeption или Throwable делает почти невозможным правильное обращение с ними при вызове вашего метода.Единственная информация, которую получает вызывающий вами метод, заключается в том, что что-то может пойти не так. Но вы не делитесь какой-либо информацией о каких-либо исключительных событиях, которые могут произойти. Вы скрываете эту информацию за обобщенными причинами выброса исключений.Становится еще хуже, когда ваше приложение меняется со временем. Выброс обобщенных исключений скрывает все изменения исключений, которые вызывающий должен ожидать и обрабатывать. Это может привести к нескольким непредвиденным ошибкам, которые необходимо найти в тестовом примере вместо ошибки компилятора.

Используйте конкретные классы

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

Public void specifySpecificExceptions() throws NumberFormatException, IllegalArgumentException { doSomething(); }

Ошибка 2: перехват обобщенных исключений

Серьезность этой ошибки зависит от того, какой программный компонент вы реализуете, и где вы обнаруживаете исключение. Возможно, было бы хорошо поймать java.lang.Exception в основном методе вашего приложения Java SE. Но вы должны предпочесть поймать определенные исключения, если вы реализуете библиотеку или работаете над более глубокими слоями вашего приложения.

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

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

Try { doSomething(); } catch (NumberFormatException e) { // handle the NumberFormatException log.error(e); } catch (IllegalArgumentException e) { // handle the IllegalArgumentException log.error(e); }

Ошибка 3: Логирование и проброс исключений

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

1. У вас недостаточно информации о прецеденте, который хочет реализовать вызывающий объект вашего метода. Исключение может быть частью ожидаемого поведения и обрабатываться клиентом. В этом случае нет необходимости регистрировать его. Это добавит ложное сообщение об ошибке в файл журнала, который должен быть отфильтрован вашей операционной группой.

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

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

Регистрируйте исключение там, где вы его обрабатываете

Таким образом, лучше всего регистрировать исключение тогда, когда вы его обрабатываете. Как в следующем фрагменте кода. Метод doSomething генерирует исключение. Метод doMore просто указывает его, потому что у разработчика недостаточно информации для его обработки. Затем он обрабатывается в методе doEvenMore, который также записывает сообщение журнала.

Public void doEvenMore() { try { doMore(); } catch (NumberFormatException e) { // handle the NumberFormatException } catch (IllegalArgumentException e) { // handle the IllegalArgumentException } } public void doMore() throws NumberFormatException, IllegalArgumentException { doSomething(); } public void doSomething() throws NumberFormatException, IllegalArgumentException { // do something }

Ошибка 4: использование исключений для управления потоком

Использование исключений для управления потоком вашего приложения считается анти-шаблоном по двум основным причинам:

Они в основном работают как оператор Go To, потому что они отменяют выполнение блока кода и переходят к первому блоку catch, который обрабатывает исключение. Это делает код очень трудным для чтения.

Они не так эффективны, как общие структуры управления Java. Как видно из названия, вы должны использовать их только для исключительных событий, а JVM не оптимизирует их так же, как и другой код.Таким образом, лучше использовать правильные условия, чтобы разбить свои циклы или инструкции if-else, чтобы решить, какие блоки кода должны быть выполнены.

Ошибка 5: удалить причину возникновения исключения

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

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

Ошибка 6: Обобщение исключений

Когда вы обобщаете исключение, вы ловите конкретный, например, NumberFormatException, и вместо этого генерируете неспецифическое java.lang.Exception. Это похоже, но даже хуже, чем первая ошибка, которую я описал в этой статье. Он не только скрывает информацию о конкретном случае ошибки на вашем API, но также затрудняет доступ.

Public void doNotGeneralizeException() throws Exception { try { doSomething(); } catch (NumberFormatException e) { throw new Exception(e); } catch (IllegalArgumentException e) { throw new Exception(e); } }
Как вы можете видеть в следующем фрагменте кода, даже если вы знаете, какие исключения может вызвать метод, вы не можете просто их поймать. Вам нужно поймать общий класс Exception и затем проверить тип его причины. Этот код не только громоздкий для реализации, но его также трудно читать. Становится еще хуже, если вы сочетаете этот подход с ошибкой 5. Это удаляет всю информацию об исключительном событии.

Try { doNotGeneralizeException(); } catch (Exception e) { if (e.getCause() instanceof NumberFormatException) { log.error("NumberFormatException: " + e); } else if (e.getCause() instanceof IllegalArgumentException) { log.error("IllegalArgumentException: " + e); } else { log.error("Unexpected exception: " + e); } }
Итак, какой подход лучший?

Будьте конкретны и сохраняйте причину возникновения исключения.

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

Try { doSomething(); } catch (NumberFormatException e) { throw new MyBusinessException(e, ErrorCode.CONFIGURATION_ERROR); } catch (IllegalArgumentException e) { throw new MyBusinessException(e, ErrorCode.UNEXPECTED); }

Ошибка 7: добавление ненужных преобразований исключений

Как я уже объяснял ранее, может быть полезно обернуть исключения в пользовательские, если вы установите исходное исключение в качестве причины. Но некоторые архитекторы переусердствуют и вводят специальный класс исключений для каждого архитектурного уровня. Таким образом, они улавливают исключение в уровне персистентности и переносят его в MyPersistenceException. Бизнес-уровень ловит и обертывает его в MyBusinessException, и это продолжается до тех пор, пока оно не достигнет уровня API или не будет обработано.

Public void persistCustomer(Customer c) throws MyPersistenceException { // persist a Customer } public void manageCustomer(Customer c) throws MyBusinessException { // manage a Customer try { persistCustomer(c); } catch (MyPersistenceException e) { throw new MyBusinessException(e, e.getCode()); } } public void createCustomer(Customer c) throws MyApiException { // create a Customer try { manageCustomer(c); } catch (MyBusinessException e) { throw new MyApiException(e, e.getCode()); } }
Легко видеть, что эти дополнительные классы исключений не дают никаких преимуществ. Они просто вводят дополнительные слои, которые оборачивают исключение. И хотя было бы забавно обернуть подарок во множестве красочной бумаги, это не очень хороший подход к разработке программного обеспечения.

Обязательно добавьте информацию

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

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

Public void persistCustomer(Customer c) { // persist a Customer } public void manageCustomer(Customer c) throws MyBusinessException { // manage a Customer throw new MyBusinessException(e, e.getCode()); } public void createCustomer(Customer c) throws MyBusinessException { // create a Customer manageCustomer(c); }

Оператор throw используется для возбуждения исключения «вручную». Для того чтобы сделать это, нужно иметь объект подкласса класса Throwable, который можно либо получить как параметр оператора catch, либо создать с помощью оператора new. Ниже приведена общая форма оператораthrow ,

throw ОбъектТипа Throwable ;

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

class ThrowDemo {
static void demoproc() {
{
throw new NullPointerException("demo");
}
catch (NullPointerException e) {
System.out.println("caught inside demoproc");
throw e;
}
}

try {
demoproc();
}
catch(NulPointerException e) {
System.out.println("recaught:" + e);
}
}
}

В этом примере обработка исключения проводится в два приема. Метод main создает контекст для исключения и вызывает demoproc. Метод demoproc также устанавливает контекст для обработки исключения, создает новый объект класса NullPointerException и с помощью оператора throw возбуждает это исключение. Исключение перехватывается в следующей строке внутри метода demoproc, причем объект-исключение доступен коду обработчика через параметр «е». Код обработчика выводит сообщение о том, что возбуждено исключение, а затем снова возбуждает его с помощью оператора throw, в результате чего оно передается обработчику исключений в методе main. Ниже приведен результат, полученный при запуске этого примера.

С:\> java ThrowDemo
caught inside demoproc
recaught: java.lang.NullPointerException: demo

9.8. Оператор throws

Если метод способен возбуждать исключения, которые он сам не обрабатывает, он должен объявить о таком поведении, чтобы вызывающие методы могли защитить себя от этих исключений. Для задания списка исключений, которые могут возбуждаться методом, используется оператор throws. Если метод в явном виде (т.е. с помощью оператора throw) возбуждает исключение соответствующего класса, тип класса исключений должен быть указан в операторе throws в объявлении этого метода. С учетом этого наш прежний синтаксис определения метода должен быть расширен следующим образом:

тип имя_метода(список аргументов) throws список исюпочений {}

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

class ThrowsDemo 1 {
static void procedure ()
{
System.out.println("inside procedure");
}
public static void main(String args) {
procedure ();
}
}

Для того чтобы мы смогли оттранслировать этот пример, нам придется сообщить транслятору, что procedure может возбуждать исключения типа IllegalAccessException и в методе main добавить код для обработки этого типа исключений:

class ThrowsDemo {
static void procedure() throws IllegalAccessException
{
System.out.println(" inside procedure");
throw new IllegalAccessException("demo");
}
public static void main(String aigs) {
try {
procedure();
}
catch (IllegalAccessException e) {
System.out.println("caught" + e);
}
}
}

Ниже приведен результат выполнения этой программы.

С :\> java ThrowsDemo
inside procedure
caught javaJangIllegalAccessException: demo

What is Exception?

Exception is an event that interrupts the normal flow of execution. It is a disruption during the execution of the Java program.

In this tutorial, you will learn-

There are two types of errors:

  1. Compile time errors
  2. Runtime errors

Compile time errors can be again classified again into two types:

  • Syntax Errors
  • Semantic Errors

Syntax Errors Example:

Instead of declaring int a; you mistakenly declared it as in a; for which compiler will throw an error.

Example: You have declared a variable int a; and after some lines of code you again declare an integer as int a; . All these errors are highlighted when you compile the code.

Runtime Errors Example

A Runtime error is called an Exceptions error. It is any event that interrupts the normal flow of program execution.

Example for exceptions are, arithmetic exception, Nullpointer exception, Divide by zero exception, etc.

Exceptions in Java are something that is out of developers control.

Why do we need Exception?

Suppose you have coded a program to access the server. Things worked fine while you were developing the code.

During the actual production run, the server is down. When your program tried to access it, an exception is raised.

How to Handle Exception

So far we have seen, exception is beyond developer"s control. But blaming your code failure on environmental issues is not a solution. You need a Robust Programming, which takes care of exceptional situations. Such code is known as Exception Handler.

In our example, good exception handling would be, when the server is down, connect to the backup server.

To implement this, enter your code to connect to the server (Using traditional if and else conditions).

You will check if the server is down. If yes, write the code to connect to the backup server.

Such organization of code, using "if" and "else" loop is not effective when your code has multiple java exceptions to handle.

Class connect{ if(Server Up){ // code to connect to server } else{ // code to connect to BACKUP server } }

Try Catch Block

Java provides an inbuilt exceptional handling.

  1. The normal code goes into a TRY block.
  2. The exception handling code goes into the CATCH block

In our example, TRY block will contain the code to connect to the server. CATCH block will contain the code to connect to the backup server.

In case the server is up, the code in the CATCH block will be ignored. In case the server is down, an exception is raised, and the code in catch block will be executed.

So, this is how the exception is handled in Java.

Syntax for using try & catch

Try{ statement(s) } catch (exceptiontype name){ statement(s) }

Step 1) Copy the following code into an editor

Class JavaException { public static void main(String args){ int d = 0; int n = 20; int fraction = n/d; System.out.println("End Of Main"); } }

Step 2) Save the file & compile the code. Run the program using command, java JavaException

Step 3) An Arithmetic Exception - divide by zero is shown as below for line # 5 and line # 6 is never executed

Step 4) Now let"s see examine how try and catch will help us to handle this exception. We will put the exception causing the line of code into a try block, followed by a catch block. Copy the following code into the editor.

Class JavaException { public static void main(String args) { int d = 0; int n = 20; try { int fraction = n / d; System.out.println("This line will not be Executed"); } catch (ArithmeticException e) { System.out.println("In the catch Block due to Exception = " + e); } System.out.println("End Of Main"); } }

Step 5) Save, Compile & Run the code.You will get the following output

As you observe, the exception is handled, and the last line of code is also executed. Also, note that Line #7 will not be executed because as soon as an exception is raised the flow of control jumps to the catch block.

Note: The AritmeticException Object "e" carries information about the exception that has occurred which can be useful in taking recovery actions.

Java Exception class Hierarchy

After one catch statement executes, the others are bypassed, and execution continues after the try/catch block. The nested catch blocks follow Exception hierarchy.
  • All exception classes in Java extend the class ‘Throwable’. Throwable has two subclasses, Error and Exception
  • The Error class defines the exception or the problems that are not expected to occur under normal circumstances by our program, example Memory error, Hardware error, JVM error, etc
  • The Exception class represents the exceptions that can be handled by our program, and our program can be recovered from this exception using try and catch block
  • A Runtime exception is a sub-class of the exception class. The Exception of these type represents exception that occur at the run time and which cannot be tracked at the compile time. An excellent example of same is divide by zero exception, or null pointer exception, etc
  • IO exception is generated during input and output operations
  • Interrupted exceptions in Java, is generated during multiple threading.
Example: To understand nesting of try and catch blocks

Step 1)

Class JavaException { public static void main(String args) { try { int d = 1; int n = 20; int fraction = n / d; int g = { 1 }; g = 100; } /*catch(Exception e){ System.out.println("In the catch clock due to Exception = "+e); }*/ catch (ArithmeticException e) { System.out.println("In the catch clock due to Exception = " + e); } catch (ArrayIndexOutOfBoundsException e) { System.out.println("In the catch clock due to Exception = " + e); } System.out.println("End Of Main"); } }

Step 2) Save the file & compile the code. Run the program using command, java JavaException .

Step 3) An ArrayIndexOutOfBoundsException is generated. Change the value of int d to 0. Save, Compile & Run the code.

Step 4 ) An ArithmeticException must be generated.

Step 5) Uncomment line #10 to line #12. Save, Compile & Run the code.

Step 6) Compilation Error? This is because Exception is the base class of ArithmeticException Exception. Any Exception that is raised by ArithmeticException can be handled by Exception class as well.So the catch block of ArithmeticException will never get a chance to be executed which makes it redundant. Hence the compilation error.

Java Finally Block

The finally block is executed irrespective of an exception being raised in the try block. It is optional to use with a try block.

Try { statement(s) } catch (ExceptiontType name) { statement(s) } finally { statement(s) }

In case, an exception is raised in the try block, finally block is executed after the catch block is executed.

Step 1) Copy the following code into an editor.

Class JavaException { public static void main(String args){ try{ int d = 0; int n =20; int fraction = n/d; } catch(ArithmeticException e){ System.out.println("In the catch clock due to Exception = "+e); } finally{ System.out.println("Inside the finally block"); } } }

Step 2) Save, Compile & Run the Code.

Step 3) Expected output. Finally block is executed even though an exception is raised.

Step 4) Change the value of variable d = 1. Save, Compile and Run the code and observe the output.Bottom of Form

Summary :

  • An Exception is a run-time error which interrupts the normal flow of program execution.Disruption during the execution of the program is referred as error or exception.
  • Errors are classified into two categories
    • Compile time errors – Syntax errors, Semantic errors
    • Runtime errors- Exception
  • A robust program should handle all exceptions and continue with its normal flow of program execution. Java provides an inbuilt exceptional handling method
  • Exception Handler is a set of code that handles an exception . Exceptions can be handled in Java using try & catch.
  • Try block : Normal code goes on this block.
  • Catch block : If there is error in normal code, then it will go into this block

О чем и зачем эта статья?

В статье рассказывается об исключениях в языке Java, методах их обработки и некоторых особенностях работы с исключениями. Статья написана для семинара по технологиям Java, проводимого компанией i.Point .

Что такое исключения?

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

Итак, каждый раз, когда при выполнении программы происходит ошибка, то программа выбрасывает исключение - в этот момент создается специальный объект-исключение (exception-object), дальше будем называть его просто исключение. Этот объект содержит информацию о возникшей ошибке: тип ошибки, а также запись о состоянии программы на момент возникновения ошибки. Создание исключения и передача его среде выполнения называется выбрасыванием исключения (exception throwing). Посмотрим, как происходит выброс исключений при выполнении программы в Java:

Во время выполнения первого метода возникла какая-то проблема, после этого было сгенерировано исключение. В момент генерации исключения Java начинает искать для него подходящий обработчик (handler), передавая объект вверх по стеку вызовов до тех пор, пока обработчик исключения не будет найден.

Типы исключений в Java

Взглянем на иерархию классов объектов-исключений в Java:

Как мы видим, все исключения имеют общего предка - Throwable. Он имеет два важных подкласса - Exception и Error. Исключения (Exceptions) являются результатом возникших внутри программы проблем, которые в принципе решаемы и предсказуемы. Ошибки (Errors) представляют собой более серьёзные проблемы, которые, согласно спецификации Java, не следует пытаться перехватить в приложении, написанном достаточно рационально (например ошибка OutOfMemoryError происходит в тех случаях, когда JVM не хватает памяти для выполнения программы). Кроме того, у Exception есть важный потомок - RuntimeException (исключение времени выполнения). Этот класс и его потомки представляют собой исключения, которые возникают во время "нормальной работы Java-машины" (примерами таких исключений являются попытки использования нулевых ссылок на объекты, деления на ноль или выход за границы массива).

В Java все исключения делятся на три типа: контролируемые исключения(checked), ошибки (Errors) и исключения времени выполнения (RuntimeExceptions) - последние два типа также объединяют в категорию неконтролируемых (unchecked) исключений. В чем различие? Все очень просто контролируемые исключения представляют собой те ошибки, которые могут быть обработаны в ходе выполнения программы, как вы уже догадались к этому типу относятся все потомки класса Exception (но не RuntimeException). Контролируемые исключения обязательны для обработки в коде программы, они должны быть обработаны либо включением в блок try-catch, либо объявлены в сигнатуре метода.

Неконтролируемые (unchecked) исключения не требуют обязательной обработки, поскольку представляют собой те ситуации, когда ошибка не зависит непосредственно от программиста (например произошёл сбой в аппаратном обеспечении), либо те, когда ошибку обрабатывать не имеет смысла, т.к. проще внести изменения в код - к ним относятся все потомки классов Error и RuntimeException.

Как обрабатывать исключения?

Все современные реализации языка Java придерживаются принципа обработки или объявления исключений (The Catch or Specify Requirement), который гласит, что код, который потенциально может сгенерировать контроллируемое исключение должен либо быть заключен в блок try-catch (таким образом в блоке catch мы предоставляем обработчик для исключительной ситуации), либо мы должны объявить , что наш метод может выбросить такое исключение (после ключевого слова throws, после имени метода).

Рассмотрим несколько примеров:

// Note: This class won"t compile by design! import java . io .*; import java . util . Vector ; public class ListOfNumbers { private Vector vector ; private static final int SIZE = 10 ; public ListOfNumbers () { vector = new Vector (SIZE ) ; for (int i = 0 ; i < SIZE ; i ++ ) { vector . addElement (new Integer (i )) ; } } public void writeList () { < SIZE ; i ++ ) { out . println (" Value at: " + i + " = " + vector . elementAt (i )) ; } out . close () ; } }

Этот код не скомпилируется, т.к. конструктор FileWriter требует от нас обработать IOException. Правильно написанный код должен выглядеть примерно так:

Try { //Необработанное контролируемое (checked) исключение IOException PrintWriter out = new PrintWriter (new FileWriter (" OutFile.txt " )) ; for (int i = 0 ; i < SIZE ; i ++ ) { // метод elementAt выбрасывает неконтролируемое исключение ArrayIndexOutOfBoundsException out . println (" Value at: " + i + " = " + vector . elementAt (i )) ; } catch (IOException e ){ //Пытаемся как-то исправить ситуацию, если ошибка возникла при создании файла OutFile.txt } catch (Exception e ){ //В случае если в блоке try будет сгенерировано не IOException, то управление перейдет сюда } finally { //В любом случае нам необходимо закрыть файл. if (out != null ){ out . close () ; } }

Таким образом мы обрабатываем исключение IOException. Обратите внимание на порядок объявления блоков catch - если поменять их местами, то код не скомпилируется, т.к. IOException подкласс Exception, то код обработки IOException станет недостижимым и компилятор выдаст ошибку. Особого внимания заслуживает блок finally - код в этом блоке выполняется всегда, независимо от того, что произошло в блоках try и catch.

Кроме непосредственно обработки исключения с помощью блока try-catch мы можем просто его объявить, предоставив пользователям метода самим разбираться с этой проблемой:

Public void writeList () throws IOException { //теперь нам не нужно самим обрабатывать исключение. }

Обрабатывать или объявлять?

Когда нам следует обрабатывать исключения, а когда объявлять? Очень простой вопрос.. жаль что на него нет однозначного ответа. В целом, следует придерживаться следующего правила:

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

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

Преимущества, которые дает нам использование исключений

написать тут про errorcodes и раскрыть списочег

  1. Разделение обычного кода и кода обработки ошибок
  2. Возможность передачи исключений для обработки вверх по стеку вызовов
  3. Группировка и обработка ошибок по типам

Проблемы, связанные с обработкой исключений

"Потерянные исключения"

Вообще, исключения в Java очень удобны и просты, но, к сожалению, и у них есть недостаток. Хотя исключения являются индикаторами проблем в программе и не должны игнорироваться, возможна ситуация, при которой исключение просто потеряется. Это может произойти, если мы неправильно напишем код в блоке finally. Рассмотрим простой пример:

// Как может быть потеряно исключение. class VeryImportantException extends Exception { public String toString () { return " A very important exception! " ; } } class HoHumException extends Exception { public String toString () { return " A trivial exception " ; } } public class LostMessage { void f () throws VeryImportantException { throw new VeryImportantException () ; } void dispose () throws HoHumException { throw new HoHumException () ; } public static void main (String args ) throws Exception { LostMessage lm = new LostMessage () ; try { lm . f () ; } finally { lm . dispose () ; } } }

Что же мы получим в результате выполнения этого кода?

Exception in thread "main" A trivial exception
at LostMessage.dispose(LostMessage.java:21)
at LostMessage.main(LostMessage.java:29)

О ужас, мы потеряли очень важное для нас VeryImportantException, получив вместо него менее значительное.
Поэтому при написании кода в блоке finally нужно быть очень осторожными, чтобы не происходило подобных потерь информации.

Последнее обновление: 30.10.2015

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

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

Int numbers = new int; numbers=45; System.out.println(numbers);

Так как у нас массив numbers может содержать только 3 элемента, то при выполнении инструкции numbers=45 консоль отобразит исключение, и выполнение программы будет завершено. Теперь попробуем обработать это исключение:

Try{ int numbers = new int; numbers=45; System.out.println(numbers); } catch(Exception ex){ ex.printStackTrace(); } System.out.println("Программа завершена");

При использовании блока try...catch вначале выполняются все инструкции между операторами try и catch. Если в блоке try вдруг возникает исключение, то обычный порядок выполнения останавливается и переходит к инструкции сatch. Поэтому когда выполнение программы дойдет до строки numbers=45; , программа остановится и перейдет к блоку catch

Выражение catch имеет следующий синтаксис: catch (тип_исключения имя_переменной) . В данном случае объявляется переменная ex , которая имеет тип Exception . Но если возникшее исключение не является исключением типа, указанного в инструкции сatch, то оно не обрабатывается, а программа просто зависает или выбрасывает сообщение об ошибке.

Но так как тип Exception является базовым классом для всех исключений, то выражение catch (Exception ex) будет обрабатывать практически все исключения. Обработка же исключения в данном случае сводится к выводу на консоль стека трассировки ошибки с помощью метода printStackTrace() , определенного в классе Exception.

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

Конструкция try..catch также может иметь блок finally . Однако этот блок необязательный, и его можно при обработке исключений опускать. Блок finally выполняется в любом случае, возникло ли исключение в блоке try или нет:

Try{ int numbers = new int; numbers=45; System.out.println(numbers); } catch(Exception ex){ ex.printStackTrace(); } finally{ System.out.println("Блок finally"); } System.out.println("Программа завершена");

Обработка нескольких исключений

В Java имеется множество различных типов исключений, и мы можем разграничить их обработку, включив дополнительные блоки catch:

Int numbers = new int; try{ numbers=45; numbers=Integer.parseInt("gfd"); } catch(ArrayIndexOutOfBoundsException ex){ System.out.println("Выход за пределы массива"); } catch(NumberFormatException ex){ System.out.println("Ошибка преобразования из строки в число"); }

Если у нас возникает исключение определенного типа, то оно переходит к соответствующему блоку catch.

Оператор throw

Чтобы сообщить о выполнении исключительных ситуаций в программе, можно использовать оператор throw . То есть с помощью этого оператора мы сами можем создать исключение и вызвать его в процессе выполнения. Например, в нашей программе происходит ввод числа, и мы хотим, чтобы, если число больше 30, то возникало исключение:

Package firstapp; import java.util.Scanner; public class FirstApp { public static void main(String args) { try{ Scanner in = new Scanner(System.in); int x = in.nextInt(); if(x>=30){ throw new Exception("Число х должно быть меньше 30"); } } catch(Exception ex){ System.out.println(ex.getMessage()); } System.out.println("Программа завершена"); } }

Здесь для создания объекта исключения используется конструктор класса Exception, в который передается сообщение об исключении. И если число х окажется больше 29, то будет выброшено исключение и управление перейдет к блоку catch.

В блоке catch мы можем получить сообщение об исключении с помощью метода getMessage() .