Исключения, try ... catch

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

Ключевые слова исключений try, catch, throw, throws, finally

Механизм исключительных ситуаций в Java поддерживается пятью ключевыми словами:

  • try,
  • catch,
  • throw,
  • throws,
  • finally.

Ниже приведена общая форма блока обработки исключений.

try { 
    // блок кода 
} catch (<ExceptionType1> е)  { 
    // обработчик исключений типа ExceptionType1
} catch (<ExceptionType2> е) { 
    // обработчик исключений типа ExceptionType2 
} finally {
    // ...  
}

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

В вершине иерархии исключений стоит класс Throwable, который наследуется от Object. Каждый из типов исключений является подклассом Throwable. Два непосредственных наследника класса Throwable делят иерархию подклассов исключений на две различные ветви. Иерархия классов представлена на рисунке.

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

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

Неперехваченные исключения

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

package samples;

class TestException
{ 
    public static void main(String args[])
    { 
        int d = 0; 
        int a = 42 / d; 
        System.out.println ("a = " + a);
    }
} 

В консоль будет выведено следующее сообщение.


Exception in thread "main" java.lang.ArithmeticException: / by zero
    at samples.TestException.main(TestException.java:8)
 

Следует обратить внимание на тот факт, что типом возбужденного исключения был не Exception и не Throwable. Это подкласс класса Exception, а именно: ArithmeticException, поясняющий, какая ошибка возникла при выполнении программы.

Изменим класс добавлением статического метода subroutine, в котором создадим такую же исключительную ситуацию.

package samples;

public class TestException
{
   static void subroutine()
    { 
        int d = 0; 
        int a = 10 / d; 
        System.out.println ("a = " + a);
    } 

    public static void main(String[] args)
    {
        TestException.subroutine();
    }
}

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


Exception in thread "main" java.lang.ArithmeticException: / by zero
    at samples.TestException.subroutine(TestException.java:8)
    at samples.TestException.main(TestException.java:14)
 

Перехват исключений try catch

Для защиты программного кода от исключений необходимо использовать связанные блоки с ключевыми словами try catch; catch помещается сразу же после try-блока. В блоке catch задается тип исключения, которое необходимо обработать.

class TestException
{ 
    public static void main(String args[])
    { 
        try { 
            int d = 0; 
            int a = 42 / d; 
        } catch (ArithmeticException e) { 
            System.out.println("division by zero"); 
        } 
    }
} 

Целью большинства хорошо сконструированных catch-разделов должна быть обработка возникшей исключительной ситуации и приведение переменных программы в некоторое разумное состояние - такое, чтобы программу можно было продолжить так, будто никакой ошибки и не было (в нашем примере выводится предупреждение - division by zero).

Несколько разделов catch

В отдельных случаях блок программного кода может вызвать исключения различных типов. Для того, чтобы локализовать обработку подобных ситуаций, можно использовать несколько catch-разделов для одного try-блока. Блоки наиболее специализированных классов исключений должны идти первыми, поскольку ни один подкласс не будет достигнут, если поставить его после суперкласса.

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

class MultiCatch
{ 
    static int c[] = { 1 }; 
    public static void main(String args[])
    { 
        try { 
            int a = args.length; 
            System.out.println("a = " + String.valueOf(a)); 
            int b = 23 / a; 
            c[4] = 33; 
        }  catch (ArithmeticException e) { 
            System.out.println("ArithmeticException : " + e.getMessage()); 
        } catch(ArrayIndexOutOfBoundsException e) { 
            System.out.println("ArrayIndexOutOfBoundsException : " + e.getMessage()); 
        } 
    }
} 

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


а = 0 
div by 0: java.lang.ArithmeticException: / by zero 

a = 1 
array index oob: java.lang.ArrayIndexOutOfBoundsException: 33
  

Вложенные операторы try

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

class MultiNest
{ 
    static int c[] = { 1 }; 
    static void checkArray()
    { 
        try { 
            c[4] = 33; 
        } catch(ArrayIndexOutOfBoundsException e) { 
            System.out.println"ArrayIndexOutOfBoundsException : " + e.getMessage()); 
        }
    } 
	public static void main(String args[])
    { 
        try { 
            int a = args.length(); 
            System.out.println("a = " + a); 
            int b = 23 / a; 
            checkArray(); 
        } catch (ArithmeticException e) { 
            System.out.println("ArithmeticException : " + e.getMessage()); 
        } 
    }
} 

Возбуждение исключений throw

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

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

class TestThrow
{ 
    static void method() 
    { 
        try { 
            throw new NullPointerException("Exception in method"); 
        } catch (NullPointerException e) { 
            System.out.println(e.getMessage());
            throw e; 
        }
    } 
    public static void main(String args[])
    { 
        try { 
            method(); 
        } catch(NullPointerException e) { 
            System.out.println("Catch inside main : " + e.getMessage()); 
        } 
    }
}

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


Exception in method
Catch inside main : Exception in method
 

Объявление об исключении throws

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

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

public class TestThrow 
{
    static void method() throws IllegalAccessException 
    { 
        try { 
            System.out.println("inside method"); 
            throw new IllegalAccessException ("Exception in method"); 
        } catch (NullPointerException e) { 
            System.out.println(e.getMessage()); 
        }
    } 
    public static void main(String args[])
    { 
        try { 
            method(); 
        } catch(IllegalAccessException  e) { 
            System.out.println("Catch inside main : " + e.getMessage()); 
        } 
    }
}

Результат работы примера:


inside method
Catch inside main : Exception in method
 

Ключевое слово finally

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

У каждого раздела try должен быть по крайней мере или один раздел catch или блок finally. Блок finally очень удобен для закрытия файлов и освобождения любых других ресурсов, захваченных для временного использования в начале выполнения метода.

Ниже приведен пример класса с двумя методами, завершение которых происходит по разным причинам, но в обоих перед выходом выполняется код раздела finally.

public class TestFinally
{
    static void methodA()
    { 
        try { 
            System.out.println("inside methodA"); 
            throw new RuntimeException("Exception in methodA"); 
        } finally { 
            System.out.println("finally inside methodA"); 
        } 
    }

    static void methodB()
    { 
        try { 
            System.out.println("inside methodB"); 
            return; 
        } finally { 
            System.out.println("finally inside methodB"); 
        }
    } 

    public static void main(String args[])
    { 
        try { 
            methodA(); 
        } catch (Exception e) {
            System.out.println("Catch exception iinside main"); 
        } 
        methodB(); 
    }
} 

В тестовом примере в методе methodA возбуждается исключение. Но перед преждевременным выходом из блока try, выполняется раздел finally. Во втором методе methodB завершается работа в try-блоке оператором return, но и при этом перед выходом из метода выполняется программный код блока finally. Результат работы тестового примера:


inside methodA
finally inside methodA
Catch exception iinside main
inside methodB
finally inside methodB
 

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

Наследование исключений

catch — полиморфная конструкция, т.е. catch по типу parent перехватывает исключения любого типа, которые является Parent'ом.

public class TestException
{
    public static void main(String[] args) {
        try {
            System.err.print("level 0");
            throw new RuntimeException();
            System.err.print("level 1");
        } catch (Exception e) { // catch по Exception ПЕРЕХВАТЫВАЕТ RuntimeException
            System.err.print("level 2");
        }
        System.err.println("level 3");
    }
}

В результате в консоли увидим


level 0
level 2
level 3
 

Error и Exception из параллельных веток наследования от Throwable, поэтому catch по одному «брату» не может поймать другого «брата».

public class TestError
{
    public static void main(String[] args)
    {
        try {
            System.err.println("level 0");
            if (true) {
                throw new Error();
            }
            System.err.println("level 1");
        } catch (Exception e) {
            System.err.println("level 2");
        }
        System.err.println("level 3");
    }
}

Результат выполения программы


level 0
Exception in thread "main" java.lang.Error
    at TestError.main(TestFinally.java:8)
 

Множественные исключения

Объявление исключений в методе может быть множественным. Пример :

import java.io.EOFException;
import java.io.FileNotFoundException;

public class MultiException
{
    // объявляем исключения
    public static void main(String[] args) throws EOFException, FileNotFoundException
    {
        if (System.currentTimeMillis() % 2 == 0) {
            throw new EOFException();
        } else {
            throw new FileNotFoundException();
        }
    }
}
Наверх
  Рейтинг@Mail.ru