Рейтинговые книги
Читем онлайн Философия Java3 - Брюс Эккель

Шрифт:

-
+

Интервал:

-
+

Закладка:

Сделать
1 ... 67 68 69 70 71 72 73 74 75 ... 132

В следующем примере создается класс, названный 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 не выполняется при неудачном конструировании и всегда выполняется, если конструирование прошло удачно.

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

Секция 3 показывает, как поступать с объектами, при конструировании которых возможны сбои и которые нуждаются в завершении. Здесь программа усложняется, потому что каждое конструирование должно заключаться в отдельную копию try-catch и за ним должна следовать конструкция try-finally, обеспечивающая завершение.

Неудобства обработки исключения в подобных случаях — веский аргумент в пользу создания конструкторов, выполнение которых заведомо обходится без сбоев (хотя это и не всегда возможно).

Идентификация исключений

Механизм обработки исключений ищет в списке «ближайший» подходящий обработчик в порядке их следования. Когда соответствие обнаруживается, исключение считается найденным и дальнейшего поиска не происходит.

Идентификация исключений не требует обязательного соответствия между исключением и обработчиком. Объект порожденного класса подойдет и для обработчика, изначально написанного для базового класса:

//: exceptions/Human.java // Перехват иерархии исключений.

class Annoyance extends Exception {} class Sneeze extends Annoyance {}

public class Human {

public static void main(String[] args) { // Перехват точного типа try {

throw new SneezeO; } catch(Sneeze s) {

System out println("Перехвачено Sneeze"). } catch(Annoyance a) {

System 0ut.println("nepexBa4eH0 Annoyance"),

}

// Перехват базового типа try {

throw new SneezeO. } catch(Annoyance a) {

System out рпп^пС'Перехвачено Annoyance").

}

}

Перехвачено Sneeze Перехвачено Annoyance *///•-

Исключение Sneeze будет перехвачено в первом блоке catch, который ему соответствует — конечно, это будет первый блок. Но, если удалить первый блок catch, оставив только проверку Annoyance, программа все равно работает, потому что она перехватывает базовый класс Sneeze. Другими словами, блок catch (Annoyance а) поймает Annoyance или любой другой класс, унаследованный от него. Если вы добавите новые производные исключения в свой метод, программа пользователя этого метода не потребует изменений, так как клиент перехватывает исключения базового класса.

1 ... 67 68 69 70 71 72 73 74 75 ... 132
На этой странице вы можете бесплатно читать книгу Философия Java3 - Брюс Эккель бесплатно.
Похожие на Философия Java3 - Брюс Эккель книги

Оставить комментарий