104

104


Конструкторы

При программировании обработки исключений всегда спрашивайте себя: «Ес¬ли произойдет исключение, будет ли все корректно завершено?» Чаще все идет более или менее безопасно, но с конструкторами возникает проблема. Конст¬руктор приводит объект в определенное начальное состояние, но может начать выполнять какое-либо действие — такое как открытие файла — которое не бу¬дет правильно завершено, пока пользователь не освободит объект, вызвав спе¬циальный завершающий метод. Если исключение произойдет в конструкторе, эти финальные действия могут быть исполнены ошибочно. А это означает, что при написании конструкторов необходимо быть особенно внимательным.

Казалось бы, блок finally решает все проблемы. Но в действительности все сложнее — ведь finally выполняется всегда, и даже тогда, когда завершающий код не должен активизироваться до вызова какого-то метода. Если сбой в кон¬структоре произойдет где-то на середине, может оказаться, что часть объекта, освобождаемая в finally, еще не была создана.

В следующем примере создается класс, названный InputFile, который откры¬вает файл и позволяет читать из него по одной строке. Он использует классы FileReader и BufferedReader из стандартной библиотеки ввода/вывода Java, кото¬рая будет изучена далее, но эти классы достаточно просты, и у вас не возникнет особых сложностей при работе с ними:

// exceptions/InputFile java // Специфика исключений в конструкторах import java io *.

public class InputFile {

private BufferedReader in,

public InputFi1eCString fname) throws Exception { try {

in = new BufferedReader(new FileReader(fname)); // Остальной код, способный возбуждать исключения } catch(FileNotFoundException e) {

System out рппШС'Невозможно открыть " + fname); // Файл не открывался, поэтому не может быть закрыт throw е; } catch(Exception е) {

// При других исключениях файл должен быть закрыт try {

in.closeO; } catch(IOException e2) {

System out.println("in.close() исполнен неудачно");

}

throw e; // Повторное возбуждение } finally {

// He закрывайте файл здесь!!!

}

}

public String getLineO { String s, try {

s = in. readLine(); продолжение & } catch(IOException е) {

throw new RuntimeExceptionC'readLineO исполнен неудачно");

}

return s;

}

public void disposeO { try {

in.closeO;

System.out.printlnC'disposeO успешен"); } catch(IOException e2) {

throw new RuntimeExceptionC'in.closeO исполнен неудачно");

}

}

} ///:-

Конструктор InputFile получает в качестве аргумента строку (String) с име¬нем открываемого файла. Внутри блока try он создает объект FileReader для это¬го файла. Класс FileReader не особенно полезен сам по себе, поэтому мы встраи¬ваем его в созданный BufferedReader, с которым и работаем, — одно из преиму¬ществ InputFile состоит в том, что он объединяет эти два действия.

Если при вызове конструктора FileReader произойдет сбой, возбуждается ис¬ключение FileNotFoundException. В этом случае закрывать файл не нужно, так как он и не открывался. Все остальные блоки catch обязаны закрыть файл, так как он уже был открыт во время входа в них. (Конечно, все было бы сложнее в случае, если бы несколько методов могли возбуждать FileNotFoundException. В таких ситуациях обычно требуется несколько блоков try.) Метод close() тоже может возбудить исключение, которое также проверяется и перехватывается — несмотря на то, что вызов находится в другом блоке catch — с точки зрения компилятора Java это всего лишь еще одна пара фигурных скобок. После вы¬полнения всех необходимых локальных действий исключение возбуждается за¬ново; ведь вызывающий метод не должен считать, что объект был благополучно создан.

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

Метод getLine() возвращает объект String со следующей строкой из файла. Он вызывает метод readLine(), способный возбуждать исключения, но они пере¬хватываются; *гаким образом, сам getLine() исключений не возбуждает. При про¬ектировании обработки исключений вы выбираете между полной обработкой исключения на определенном уровне, его частичной обработкой и передачей да¬лее того же (или другого) исключения и, наконец, простой передачей далее. Там, где это возможно, передача исключения значительно упрощает про¬граммирование. В данной ситуации метод getLine() преобразует исключение в RuntimeException, чтобы указать на ошибку в программе.

Метод dispose() должен вызываться пользователем при завершении работы с объектом InputFile. Он освобождает системные ресурсы (такие, как открытые файлы), закрепленные за объектами BufferedReader и (или) FileReader. Делать это следует только тогда, когда работа с объектом InputFile действительно будет завершена. Казалось бы, подобные действия удобно разместить в методе fina- lize(), но, как упоминалось в главе 5, вызов этого метода не гарантирован (и даже если вы знаете, что он будет вызван, то неизвестно, когда). Это один из недостатков Java: все завершающие действия, кроме освобождения памяти, не производятся автоматически, так что вам придется информировать пользо¬вателя о том, что он ответственен за их выполнение.

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

//: exceptions/Cleanup.java

// Гарантированное освобождение ресурсов.

public class Cleanup {

public static void main(String[] args) { try {

InputFile in = new InputFileC'Cleanup java"); try {

String s; int i = 1;

whileC(s = in getLineO) != null) ; // Построчная обработка .. } catch(Exception e) {

System.out.println("Перехвачено Exception в main"). e.printStackTrace(System.out); } finally {

in.disposeO;

}

} catch(Exception e) {

System out println("Сбой при конструировании InputFile"):

}

}

} /* Output:

disposeO успешен

*///:-

Присмотритесь к логике происходящего: конструирование объекта InputFile фактически заключено в собственный блок try. Если попытка завершается не¬удачей, мы входим во внешнюю секцию catch и метод dispose() не вызывается. Но, если конструирование прошло успешно, мы хотим обеспечить гарантиро¬ванное завершение, поэтому сразу же после конструирования создается новый блок try. Блок finally, выполняющий завершение, связывается с внутренним блоком try; таким образом, блок finally не выполняется при неудачном конст¬руировании и всегда выполняется, если конструирование прошло удачно.

Report Page