Исключения – советы по использованию
Обработка исключений – это встроенная возможность языка JAVA. Концепция обработки исключений позволяет сделать код более надежным и позволяет лучше читать и сопровождать его. Давайте посмотрим на конструкцию для обработки исключения:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
try { // code which could potentially // throw an exception } catch (TheException1 e) { // code to handle exceptional condition } catch (TheException2 e) { // code to handle next exceptional condition } finally { // code to run whether an exceptional // condition happened or not } |
В основном, код, который может вызвать исключение помещается в такую конструкцию. Блок catch описываемый для определенного исключения будет вызываться – т.е. если случится исключение TheException1, тогда будет вызван блок внутри его catch. Однако блок finally будет вызываться всегда, даже если какой-либо блок catch содержит return.
Таким образом думаю понятно, что блок try должен быть всегда. И всегда должен быть хотя бы один из блоков catch или finally.
Исходя из того, что блоков catch может быть много, система в случае исключения будет искать первый подходящий тип исключения. Причем с учетом наследования. Т.е. если вы ловите исключение IOException, то исключение FileNotFoundException, которрое является подклассом IOException будет обрабатываться в блоке try, который содержит IOException. Думаю, что это несложно.
Первый совет по обработке исключений: старайтесь обрабатывать специфические исключения. Т.е. если вы создали метод, который будет генерировать исключение FileNotFoundException – не декларируйте, что метод взывает исключение IOException. Нехорошо заставлять пользователя вашим классом обрабатывать исключение более высокого уровня, чем надо в действительности.
Второй совет: не делайте пустых catch блоков. Иными словами не делйте такой код.
1 2 3 4 |
try { ... } catch (AnException e) { } |
Если даже логика вашего кода подразумевает полное отсуствие каких-либо действий при исключении – не поленитесь и напишите комментарий. Иначе пользватели вашего кода будут в затруднении.
Еще один добрый совет: Если метод класса вызывает исключение – пишите на него документацию. Используйте тэг @throws.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
/** * Loads the class * * @param name * Class name * * @return Resulting <tt>Class</tt> object * * @throws ClassNotFoundException * If class not found */ public Class loadClass(String name) throws ClassNotFoundException { ... } |
Наверняка вы знаете о RuntimeException и его подклассах. Эти исключения не требуют, чтобы их обрабатывали. И в общем не всегда их обработка будет выглядет логичной и нужной.
Но в некоторых случаях это будет логично и понятно. Например если вы ожидаете от пользователя ввода целого числа, которе вводится как строка. Для преобразования скорее всего будет использован методв parseInt, который вызывает runtime исключение NumberFormatException. Обычно runtime исключения обрабатываются тогда, когда необходимо восстановление при таких исключениях. Как в выше приведенном примере с parseInt.
Новые возможности Java 1.5 – UncaughtExceptionHandler
Мы уже говорили о том, что есть non-runtime исключения, которые вы обязаны помещать в блок try/catch и runtime, которые не требуют специальной обработки. Но бывают случаи, когда требуется обрабатывать и их – например вы хотите выводить все сообщения в определенном формате в какое-то окно сообщений. Расставлять по всему коду блоки try/catch конечно не очень удобное занятие.
Существуют три способа сделать обработку runtime исключений.
Вызвать метод setUncaughtExceptionHandler() у класса Thread.
Определить свой класс ThreadGroup и переопределить метод uncaughtException().
Вызвать статический метод класса Thread — setDefaultUncaughtExceptionHandler().
Методы setUncaughtExceptionHandler() и setDefaultUncaughtExceptionHandler() принимают в качестве аргумента класс, который реализует интерфейс UncaughtExceptionHandler.
Этот интерфейс имеет всего один метод
void uncaughtException(Thread t, Throwable e)
Рассмотрим в качестве примера два класса.
Первый класс представляет из себя окно, которое будет «всплывать» при возникновении исключнения. Окно реализует интерфейс UncaughtExceptionHandler.
Надо обратить внимание на два момента:
1. Это метод uncaughtException. Он реализует интерфейс
2. Метод addStackInfo. В общем-то в нем и происхожит основная работа.
Чтобы вы не пугались — EventQueue.invokeLater представляет из себя метод, который вызывается «в очереди» и что важно отметить делается асинхронно (в отдельном треде).
Все остальные вызовы достаточно очевидны – сделать окно видимым, вывести его на передний план, создать два потока для печати строк и вывести строки. В общем-то и все.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
import java.awt.*; import java.io.*; import javax.swing.*; public class StackWindow extends JFrame implements Thread.UncaughtExceptionHandler { private JTextArea textArea; public StackWindow(String title, final int width, final int height) { super(title); setSize(width, height); textArea = new JTextArea(); JScrollPane pane = new JScrollPane(textArea); textArea.setEditable(false); getContentPane().add(pane); } public void uncaughtException(Thread t, Throwable e) { addStackInfo(e); } public void addStackInfo(final Throwable t) { EventQueue.invokeLater(new Runnable() { public void run() { // Bring window to foreground setVisible(true); toFront(); // Convert stack dump to string StringWriter sw = new StringWriter(); PrintWriter out = new PrintWriter(sw); t.printStackTrace(out); // Add string to end of text area textArea.append(sw.toString()); } }); } } |
Второй класс сделан для того, чтобы продемонстрировать работу нашего окошка. Откомпилируйте и запускайте.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
import java.io.*; public class DumpTest { public static void main(final String args[]) throws Exception { Thread.UncaughtExceptionHandler handler = new StackWindow("Show Exception Stack", 400, 200); Thread.setDefaultUncaughtExceptionHandler(handler); new Thread() { public void run() { System.out.println(1 / 0); } }.start(); BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); System.out.print("Press Enter for next exception"); br.readLine(); new Thread() { public void run() { System.out.println(args[0]); } }.start(); System.out.print("Press Enter to end"); br.readLine(); System.exit(0); } } |
При запуске вы увидите окошко с ошибкой. Его можно закрыть и в консольном окне (где запущено приложение) нажмите Enter (как предлагается). И снова увидите окно с ошибкой. Причем важно отметить, что обе ошибки – runtime, но программа нормально продолжает свою работу.