// 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 не выполняется при неудачном конст¬руировании и всегда выполняется, если конструирование прошло удачно.
Эта универсальная идиома применяется и в тех ситуациях, когда конструк¬тор не выдает исключений. Основной принцип: сразу же после создания объекта, требующего завершения, начинается конструкция try-finally:
//: exceptions/Cleanupldiom java
// За каждым освобождаемым объектом следует try-finally
class NeedsCleanup { // Конструирование не может завершиться неудачно private static long counter = 1,
private final long id = counter++, Л
продолжение &
pub.lic void disposeO {
System out printin("NeedsCleanup " + id + " завершен");
class ConstructionException extends Exception {}
class NeedsCleanup2 extends NeedsCleanup { // Возможны сбои при конструировании, public NeedsCleanup2() throws ConstructionException {}
public class Cleanupldiom {
public static void main(String[] args) { // Секция 1-
NeedsCleanup ncl = new NeedsCleanupO; try {
// .. } finally {
ncl.disposeO.
// Секция 2;
// Если сбои при конструировании исключены, // объекты можно группировать. NeedsCleanup nc2 = new NeedsCleanupO; NeedsCleanup псЗ = new NeedsCleanupO; try {
// .. } finally {
nc3 disposeO; // Обратный порядок конструирования nc2.disposeO;
// Секция 3-
// Если при конструировании возможны сбои, каждый объект // защищается отдельно; try {
NeedsCleanup2 nc4 = new NeedsCleanup20; try {
NeedsCleanup2 nc5 = new NeedsCleanup2(); try {
// ...
} finally {
nc5.disposeO;
}
} catch(ConstructionException e) { // Конструктор nc5
System.out.println(e), } finally {
nc4 disposeO;
}
} catch(ConstructionException e) { // Конструктор nc4 System.out.println(e);
}
}
} /* Output; NeedsCleanup 1 завершен NeedsCleanup 3 завершен
Идентификация исключений 343
NeedsCleanup 2 завершен NeedsCleanup 5 завершен NeedsCleanup 4 завершен */// ~
Секция 1 метода main() весьма прямолинейна: за созданием завершаемого объекта следует try-finally. Если конструирование не может завершиться неуда¬чей, наличие catch не требуется. В секции 2 мы видим, что конструкторы, кото¬рые не могут завершиться неудачей, могут группироваться как для конструиро¬вания, так и для завершения.