Чтение онлайн

ЖАНРЫ

Философия Java3

Эккель Брюс

Шрифт:

// При других исключениях файл должен быть закрыт 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 мы видим, что конструкторы, которые не могут завершиться неудачей, могут группироваться как для конструирования, так и для завершения.

Поделиться с друзьями: