- Вопросы по Java на собеседовании (3)
- 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 на собеседовании (3)
Исключение — это ошибка, возникающая во время выполнения программы. Причины возникновения исключения могут разные, например :
- некорректно определены (не определены) данные;
- невозможно прочитать или создать файл;
- обрыв сетевого соединения или соединения с сервером базы данных.
Исключение в Java является объектом. Поэтому они могут не только создаваться автоматически виртуальной машиной JVM при возникновении исключительной ситуации, но и порождаться самим разработчиком.
2. Операторы исключений
Java имеет пять ключевых операторов для определения блока исключений, перехвата и возбуждения исключений :
- try — начала блока кода, в котором может возникнуть исключение, и которое следует перехватить;
- catch — начала блока кода, предназначенного для перехвата и обработки исключений (параметром catch является тип ожидаемого исключения);
- throw — оператор для генерации исключений;
- throws — ключевое слово, используемое в сигнатуре метода, и обозначающее, что метод потенциально может вызвать исключение с определенным типом;
- 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
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)
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)
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); >>
В результате в консоль будет выведено следующее сообщение :
Чтобы не «потерять» исключение, необходимо его корректно перехватить и обработать. В примере следует убрать комментарий с блока 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. Логирование исключений
Лучше всего логировать (регистрировать) исключение в месте его обработки. Это связано с тем, что именно в данном месте кода достаточно информации для описания возникшей проблемы. Кроме этого, одно и то же исключение при вызове одного и того же метода можно перехватывать в разных местах программы; регистрировать же следует в одном месте.
Иногда исключение может быть частью ожидаемого поведения. В этом случае нет необходимости его регистрировать.