57

57


//: exceptions/NeverCaught java
// Игнорирование RuntimeExceptions.
// {ThrowsException}
public class NeverCaught {
static void f() {
throw new RuntimeException("H3 f(D;
}
static void g() { f();
}
public static void mainCString[] args) {
g():
}
} ///.-
Можно сразу заметить, что RuntimeException (и все от него унаследованное) является специальным случаем, так как компилятор не требует для него специ¬фикации исключения. Выходные данные выводятся в System.err:
Exception in thread "main" java.lang.RuntimeException- Из f() at NeverCaught.f(NeverCaught.java:7) at NeverCaught.g(NeverCaught.java:10) at NeverCaught.main(NeverCaught.java-13)
Мы приходим к ответу на поставленный вопрос: если RuntimeException доби¬рается до main() без перехвата, то работа программы завершается с вызовом ме¬тода printStackTraceO.
Помните, что только исключения типа RuntimeException (и производных классов) могут игнорироваться во время написания текста программы, в то вре¬мя как остальные действия компилятор осуществляет в обязательном порядке. Это объясняется тем, что RuntimeException является следствием ошибки про¬граммиста, например:
• ошибки, которую невозможно предвидеть (к примеру, получение null- ссылки в вашем методе, переданной снаружи);
• ошибки, которую вы как программист должны были проверить в вашей программе (подобной ArraylndexOutOfBoundsException, с проверкой разме¬ра массива). Ошибки первого вида часто становятся причиной ошибок второго вида.
В подобных ситуациях исключения оказывают неоценимую помощь в отла¬дочном процессе.
Назвать механизм исключений Java узкоспециализированным инструмен¬том было бы неверно. Да, он помогает справиться с досадными ошибками на стадии исполнения программы, которые невозможно предусмотреть заранее, но при этом данный механизм помогает выявлять многие ошибки программи¬рования, выходящие за пределы возможностей компилятора.
Завершение с помощью finally
Часто встречается ситуация, когда некоторая часть программы должна выполнять¬ся независимо от того, было или нет возбуждено исключение внутри блока try. Обычно это имеет отношение к операциям, не связанным с освобождением памя¬ти (так как это входит в обязанности сборщика мусора). Для достижения желае¬мой цели необходимо разместить блок finally  после всех обработчиков исключе¬ний. Таким образом, полная конструкция обработки исключения выглядит так:
try {
// Защищенная секция: рискованные операции, // которые могут породить исключения А, В. или С } catch(A al) {
// Обработчик для ситуации А } catch(B Ы) {
// Обработчик для ситуации В } catch(C cl) {
// Обработчик для ситуации С } finally {
// Действия, производимые в любом случае
}
Чтобы продемонстрировать, что блок finally выполняется всегда, рассмотрим следующую программу:
//: exceptions/FinallyWorks.java // Блок finally выполняется всегда
class ThreeException extends Exception {}
public class FinallyWorks { static int count = 0; public static void main(String[] args) { while(true) { try {
// Операция постфиксного приращения, в первый раз 0: if(count++ == 0)
throw new ThreeExceptionO; System.out.println("Нет исключения"); } catch(ThreeException e) {
System.out.pri ntln("ThreeExcepti on"); } finally {
System.out.println("B блоке finally"); if(count == 2) break; // вне цикла "while"
}
}
}
} /* Output: ThreeException В блоке finally Нет исключения В блоке finally *///-
Результат работы программы показывает, что вне зависимости от того, было ли возбуждено исключение, предложение finally выполняется всегда.
Данный пример также подсказывает, как справиться с тем фактом, что Java не позволяет вернуться к месту возникновения исключения, о чем говорилось ранее. Если расположить блок try в цикле, можно также определить условие, на основании которого будет решено, должна ли программа продолжаться. Так¬же можно добавить статический счетчик или иной механизм для проверки не¬скольких разных решений, прежде чем отказаться от попыток восстановления. Это один из способов обеспечения повышенной отказоустойчивости программ.
Для чего нужен блок finally?
В языках без сборки мусора и без автоматических вызовов деструкторов  блок finally гарантирует освобождение ресурсов и памяти независимо от того, что случилось в блоке try. В Java существует сборщик мусора, поэтому с освобожде¬нием памяти проблем не бывает. Также нет необходимости вызывать деструк¬торы, их просто нет. Когда же нужно использовать finally в Java?
Блок finally необходим тогда, когда в исходное состояние вам необходимо вернуть что-то другое, а не память. Это может быть, например, открытый файл или сетевое подключение, часть изображения на экране или даже какой-то фи¬зический переключатель, вроде смоделированного в следующем примере:
//: exceptions/Switch.java
import static net mindview.util.Print.*;
class Switch {
private boolean state = false; public boolean readO { return state, } public void on() { state = true, print(this); } public void offО { state = false, print(this), } public String toStringO { return state ? "on" • "off"; } } ///.-
//. exceptions/OnOffException]..java
public class OnOffExceptionl extends Exception {} lll-
ll . exceptions/0n0ffException2.java
public class 0n0ffException2 extends Exception {} III ~
//• exceptions/OnOffSwitch java 11 Для чего нужно finally?
public class OnOffSwitch {
private static Switch sw = new SwitchO; static void f()
throws OnOffExceptionl, 0n0ffException2 {} public static void main(String[] args) { try {
sw.onO;
// Код, способный возбуждать исключения... f();
sw off(): } catch(OnOffExceptionl e) {
System.out.pri ntin("OnOffExcepti onl"); sw.offO; } catch(OnOffException2 e) {
System.out.pri ntin("OnOffExcepti on2"); sw.offO:
}
}
} /* Output-
on
off
*///:-
Наша цель — убедиться в том, что переключатель был выключен по завер¬шении метода main(), поэтому в конце блока try и в конце каждого обработчика исключения помещается вызов sw.off(). Однако в программе может возникнуть неперехватываемое исключение, и тогда вызов sw.off() будет пропущен. Однако благодаря finally завершающий код можно поместить в одном определенном месте:
II: exceptions/WithFinally.java 11 Finally гарантирует выполнение завершающего кода.
public class WithFinally {
static Switch sw = new SwitchO; public static void main(String[] args) { try {
sw.onO;
// Код, способный возбуждать исключения. OnOffSwitch.fO; } catch(OnOffExceptionl e) {
System out.printing"OnOffExceptionl"); } catch(OnOffException2 e) {
System out println( OnOffException2"); } finally {
sw.offO;
}
}
} /* Output:
on
off
*///:-
Здесь вызов метода sw.off() просто перемещен в то место, где он гарантиро¬ванно будет выполнен.
Даже если исключение не перехватывается в текущем наборе условий catch, блок finally отработает перед тем, как механизм обработки исключений продол¬жит поиск обработчика на более высоком уровне:
//: exceptions/AlwaysFinally.java
// Finally выполняется всегда
import static net.mindview.util Print.*:
class FourException extends Exception {}
public class AlwaysFinally {
public static void main(String[] args) {
print("Входим в первый блок try"), try {
print("Входим во второй блок try"): try {
throw new FourExceptionO, } finally {
print("finally во втором блоке try"):
}
} catch(FourException e) { System.out.println(
"Перехвачено FourException в первом блоке try"):
} finally {
System.out.println("finally в первом блоке try"):
}
}
} /^Output-
Входим в первый блок try Входим во второй блок try finally во втором блоке try Перехвачено FourException в первом блоке try finally в первом блоке try *///:-
Блок finally также исполняется при использовании команд break и continue. Заметьте, что комбинация finally в сочетании с break и continue с метками снима¬ет в Java всякую необходимость в операторе goto.
Использование finally с return
Поскольку секция finally выполняется всегда, важные завершающие действия будут выполнены даже при возврате из нескольких точек метода:
//• excepti ons/Multi pleReturns java import static net.mindview util Print.*;
public class MultipleReturns {
public static void f(int i) {
pri nt("Инициализация. требующая завершения"), try {
print("Точка 1"), if(i == 1) return, print("Точка 2"); if(i == 2) return, print("Точка 3"), if(i == 3) return, print("Конец"), return; } finally {
ргШС'Завершение"),
}
}
public static void main(String[] args) { for (int i =1, i <=4; i++) f(i).
}
} /* Output;
Инициализация, требующая завершения
Точка 1
Завершение
Инициализация, требующая завершения Точка 1 Точка 2 Завершение
Инициализация, требующая завершения
Точка 1
Точка 2
Точка 3
Завершение
Инициализация, требующая завершения
Точка 1
Точка 2
Точка 3
Конец
Завершение *///;-
Из выходных данных видно, что выполнение finally не зависит от того, в ка¬кой точке защищенной секции была выполнена команда return.
Проблема потерянных исключений
К сожалению, реализация механизма исключений в Java не обошлась без изъяна. Хотя исключение сигнализирует об аварийной ситуации в программе и никогда
Использование finally с return 335
не должно игнорироваться, оно может быть потеряно. Это происходит при ис¬пользовании finally в конструкции определенного вида:
//: exceptions/LostMessage.java // Как теряются исключения.
class VeryImportantException extends Exception { public String toStringO {
return "Очень важное исключение!";
}
}
class HoHumException extends Exception { public String toStringO {
return "Второстепенное исключение";
}
}
public class LostMessage {
void fO throws VerylmportantException {
throw new VerylmportantExceptionO;
}
void disposeO throws HoHumException { throw new HoHumExceptionO;
}
public static void main(String[] args) { try {
LostMessage 1m = new LostMessageO; try {
lm.fO; } finally {
lm. disposeO; } catch(Exception e) {
System.out.println(e);
}
}
} /* Output:
Второстепенное исключение *///:-
В выводе нет никаких признаков VerylmportantException, оно было просто за¬мещено исключением HoHumException в предложении finally. Это очень серьез¬ный недочет, так как потеря исключения может произойти в гораздо более скрытой и трудно диагностируемой ситуации, в отличие от той, что показана в примере. Например, в С++ подобная ситуация (возбуждение второго исключе¬ния без обработки первого) рассматривается как грубая ошибка программиста. Возможно, в новых версиях Java эта проблема будет решена (впрочем, любой метод, способный возбуждать исключения — такой, как dispose() в приведенном примере — обычно заключается в конструкцию try-catch).
Еще проще потерять исключение простым возвратом из finally:
И: exceptions/ExceptionSi1encer.java
public class ExceptionSilencer {
public static void main(String[] args) { try {
throw new RuntimeExceptionO: } finally {
// Команда 'return' в блоке finally // прерывает обработку исключения return;
}
}
} ///.-
Запустив эту программу, вы увидите, что она ничего не выводит — несмотря на исключение.
Ограничения при использовании исключений