Вопросы по Java на собеседовании (3)

1. Понятие «Исключение»
2. Операторы исключений
3. Оператор throws
4. Блоки кода try/catch и try/finally
5. Может ли блок finally не выполняться?
6. Проверяемые и непроверяемые исключения
7. Возбуждение исключения
8. Определение исключения в сигнатуре метода
9. Особенность RuntimeException
10. Возбуждение исключения в методе main
11. Множественные исключения
12. Последовательность нескольких блоков catch
13. Поглащение исключений в блоке try...finally
14. Исключение SQLException
15. Ошибка Error
16. Обобщение исключений
17. Логирование исключений

Вопросы и ответы для собеседование по Java, часть 1.
Вопросы и ответы для собеседование по Java, часть 2.
Вопросы и ответы для собеседование по Java, часть 4.

1. Понятие «Исключение»

Исключение — это ошибка, возникающая во время выполнения программы. Причины возникновения исключения могут разные, например :

  • некорректно определены (не определены) данные;
  • невозможно прочитать или создать файл;
  • обрыв сетевого соединения или соединения с сервером базы данных.

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

2. Операторы исключений

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

  1. try — начала блока кода, в котором может возникнуть исключение, и которое следует перехватить;
  2. catch — начала блока кода, предназначенного для перехвата и обработки исключений (параметром catch является тип ожидаемого исключения);
  3. throw — оператор для генерации исключений;
  4. throws — ключевое слово, используемое в сигнатуре метода, и обозначающее, что метод потенциально может вызвать исключение с определенным типом;
  5. finally — начала дополнительного блока кода, размещаемый после последнего блока catch. Блок finally не является обязательным, но всегда получает управление.

Общая структура «перехвата» исключительной ситуации выглядит следующим образом :

try { 
   // код программы, который может привести к ошибке 
} catch(Exception e ) {
   // код программы для обработки исключений 
} finally { 
   // выполнение блока программы независимо от наличия исключения
}

3. Оператор throws

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

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

4. Блоки кода try/catch и try/finally

Каждый оператор try требует наличия либо catch, либо finally, либо сочетания catch и finally. Блок кода finally не является обязательным, и может отсутствовать при использовании try/catch. Оператором finally создаётся блок кода, который должен быть выполнен после завершения блока try/catch.

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

Пример использования try/finally представлен здесь.

5. Может ли блок finally не выполняться?

Код блока finally не будет исполнен, если в код программы включен предшествующий блоку finally системный выход. Следующий пример демонстрирует данную ситуацию.

try { 
    System.exit(0); 
} catch(Exception e) { 
    System.err.println(e.getMessage()); 
} finally {
  // код блока
}

6. Проверяемые и непроверяемые исключения

Все исключения делятся на «проверяемые» (checked) и «непроверяемые» (unchecked). Данное свойство присуще базовому классу исключения Throwable и передается по наследству (Error, Exception, RuntimeException). В исходном коде класса исключения данное свойство недоступно. Ниже представлена иерархия классов исключений.

              Object
                |
         Throwable(CHECKED)
          /            \
 Error(UNCHECKED)    Exception(CHECKED)
                          |
              RuntimeException(UNCHECKED)

Исключения Throwable и Exception, а также все их наследники, за исключением Error и RuntimeException, являются «проверяемыми» checked исключениями. Error и RuntimeException, а также все их наследники, относятся к «непроверяемым» unchecked исключениям.

Проверка исключения на checked выполняется компилятором (compile-time checking). Непроверяемые исключения можно перехватить (catch) в момент исполнения программы (runtime checking).

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

public class TestException
{
    public static Double divide(int i1, int i2) throws Exception {
        if (i2 != 0)
            return new Double (i2 / i2);
        else {
            Throwable e = new Exception();
            throw e;   // Unhandled exception type Throwable
        }
    }
    public static void main(String[] args) {
        Object obj = "Hello!";
        char   c   = obj.charAt(0); // The method charAt(int) is
                                // undefined for the type Object
    }
}

Сигнатура метода divide (операция деления) включает определение возможного исключения типа Exception, наследуемого от Throwable. Если делитель (i2) равен 0, то создается объект исключения типа Throwable и возбуждается исключение (throw t), которое не пропускает компилятор, т.к. тип возбуждаемого исключения не соответствует типу исключения в сигнатуре метода. Если в сигнатуре метода определить исключение типа Throwable, то ошибки не будет.

В методе main определен объект obj с инициализацией определенным значением. В следующей строке компилятор находит ошибку, связанную с отсутствием метода charAt(int) в объекте типа Object. Если выполнить приведение типа obj к String, то компилятор пропустит код : char c = ((String)ref).charAt(0).

Пример unchecked исключения представлен здесь.

7. Возбуждение исключения

Как было отмечено выше, исключение является объектом, который можно создать программно. Чтобы возбудить (выбросить) исключение используется оператор throw.

Exception e = new SQLException();
throw e;

8. Определение исключения в сигнатуре метода

В сигнатуре метода можно определить возможное проверяемое (checked) исключение. Следующий пример демонстрирует определение исключения в сигнатуре метода f1() и его перехват в конструкторе класса.

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

public class TestException 
{
    public TestException()
    {
        try {
            f1();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        
    }

    public void f1() throws FileNotFoundException {
        throw new FileNotFoundException();
    }

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

9. Особенность RuntimeException

Исключение RuntimeException расширяет свойства Exception и является базовым классом для ошибок во время выполнения приложения. Данное исключение относится к необрабатываемым исключениям (unchecked). Согласно описанию класса это исключение может возникнуть во время нормальной работы JVM.

Следующий код демонстрирует пример использования непроверяемого исключения NumberFormatException (наследующего свойства RuntimeException). В функции parseInt при преобразовании строкового значения в число в режиме run-time может возникнуть исключение. Можно метод функции Integer.parseInt() «обернуть» в try/catch, а можно передать обработку исключения функции в вызывающий метод, для чего в сигнатуре определяется соответствующее исключение (throws).

public int parseInt(String s) throws NumberFormatException
{
    return Integer.parseInt(s);
}

10. Возбуждение исключения в методе main

Если в методе main возбудить исключение, то оно будет передано в виртуальную машину Java (JVM).

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

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

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

public class TestException 
{
    public void callMethods() 
                throws EOFException, FileNotFoundException
    {
        if (f1())
            f0();
    }
    public void f0() throws EOFException {
        // ...
        throw new EOFException();
    }

    public boolean f1() throws FileNotFoundException {
        // ...
        throw new FileNotFoundException();
    }
}

Чтобы перехватить несколько возможных исключений можно искользовать конструкцию try с несколькими catch.

try {
    if (f1())
        f0();
} catch (FileNotFoundException e) {
    e.printStackTrace();
} catch (EOFException e) {
    e.printStackTrace();
}

12. Последовательность нескольких блоков catch

При определение нескольких блоков catch следует руководствоваться правилом обработки исключений от «младшего» к старшему. Т.е. нельзя размещать первым блоком catch (Exception e) {...}, поскольку все остальные блоки catch() уже не смогут перехватить исключение. Помните, что Exception является базовым классом, поэтому его стоит размещать последним.

Рассмотрим следующую иерархию наследования исключений :


java.lang.Object
    java.lang.Throwable
        java.lang.Exception
            java.io.IOException
                java.io.EOFException
 

Cамым младшим исключением является EOFException, поэтому он должен располагаться перед IOException и Exception, если используется несколько блоков catch с данными типами исключений. Следующий код является демонстрацией данного принципа.

String x = "Hello";
try {
    if (!x.equals("Hello"))
        throw new IOException();
    else
        throw new EOFException();
} catch (EOFException e) {
    System.err.println("EOFException : " + e.getMessage());
} catch (IOException e) {
    System.err.println("IOException : " + e.getMessage());
} catch (Exception e) {
    System.err.println("Exception : " + e.getMessage());
}

13. Поглащение исключений в блоке try...finally

Если было вызвано два исключения — одно в блоке try, а второе в finally — то, при отсутствии catch, исключение в finally «проглотит» предыдущее исключение. Следует блоки с возможными исключениями всегда обрамлять операторами try/catch, чтобы не потерять важную информацию. Следующий пример демонстрирует «поглащение» исключения в блоке try новым исключением в блоке finally.

public class TestException 
{
    public TestException() {
        try {
            System.out.println(absorbingEx());
        } catch (EOFException e) {
            System.out.println(e.getMessage());
        } catch (IOException e) {
            System.out.println(e.getMessage());
        }
    }

    public String absorbingEx() throws IOException, 
                                       EOFException 
    {
        try {
            throw new EOFException("EOFException");
//      } catch (EOFException e) {
//          System.out.println("catch " + e.getMessage());
        } finally {
            throw new IOException("finally IOException");
        }
    }

    public static void main(String[] args) {
        new TestException();
        System.exit(0);
    }
}

В результате в консоль будет выведено следующее сообщение :


finally IOException
 

Чтобы не «потерять» исключение, необходимо его корректно перехватить и обработать. В примере следует убрать комментарий с блока catch.

14. Исключение SQLException

Исключение SQLException связано с ошибками при работе с базой данных. Данное исключением относится к checked исключениям, и, следовательно, проверяется на этапе компиляции.


java.lang.Object
    java.lang.Throwable
        java.lang.Exception
            java.sql.SQLException
 

Споры вокруг SQLException связаны с тем, что исключение возникает во время исполнения, а обрабатывать его приходится в коде, чтобы не ругался компилятор; может быть следовало бы отнести его к unchecked run-time исключениям? Убедительный довод разработчиков данного исключения связан с тем, что необходимо программисту обработать свои возможные ошибки при работе с базой данных.

15. Ошибка Error

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

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

16. Обобщение исключений

При определении в сигнатуре метода возможных исключений можно вместо нескольких проверяемых исключений указать общее (базовое) java.lang.Throwable. В этом случае, компилятор «пропустит код» и программа возможно отработает без сбоев. Например :

public void callCommonException() throws Exception {
    Object obj = new Object();
    method(obj);
}
public void method(Object obj) 
                      throws NumberFormatException,
                             IllegalArgumentException {
    // ...
}

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

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

17. Логирование исключений

Лучше всего логировать (регистрировать) исключение в месте его обработки. Это связано с тем, что именно в данном месте кода достаточно информации для описания возникшей проблемы. Кроме этого, одно и то же исключение при вызове одного и того же метода можно перехватывать в разных местах программы; регистрировать же следует в одном месте.

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

Вопросы и ответы для собеседование по Java, часть 1.
Вопросы и ответы для собеседование по Java, часть 2.
Вопросы и ответы для собеседование по Java, часть 4.

  Рейтинг@Mail.ru