50

50


at DynamicFields.setField(DynamicFields.java 66) )
Каждый объект DynamicFields содержит массив пар Object-Object. Первый объ¬ект содержит идентификатор поля (String), а второй объект — значение поля, которое может быть любого типа, кроме неупакованных примитивов. При соз¬дании объекта необходимо оценить примерное количество полей. Метод setField() либо находит уже существующее поле с заданным именем, либо создает новое поле и сохраняет значение. Когда пространство для полей заканчивается, метод наращивает его, создавая массив размером на единицу больше и копи¬руя в него старые элементы. При попытке размещения пустой ссылки null ме¬тод инициирует исключение DynamicFieldsException, создавая объект нужного типа и передавая методу initCause() в качестве причины исключение NullPointer- Exception.
Для возвращаемого значения метод setField() использует старое значение поля, получая его методом getField(), который может возбудить исключение NoSuchFieldException. Если метод getField() вызывает программист-клиент, то он ответственен за обработку возможного исключения NoSuchFieldException, однако, если последнее возникает в методе setField(), это является ошибкой программы; соответственно, полученное исключение преобразуется в исключение Runtime- Exception с помощью конструктора, принимающего аргумент-причину.
Для создания результата toStringO использует объект StringBuilder. Этот класс будет подробно рассмотрен при описании работы со строками.
Стандартные исключения Java
Класс Java Throwable описывает все объекты, которые могут возбуждаться как исключения. Существует две основные разновидности объектов Throwable (то есть ветви наследования). Тип Error представляет системные ошибки и ошибки времени компиляции, которые обычно не перехватываются (кроме нескольких особых случаев). Тип Exception может быть возбужден из любого метода стандартной библиотеки классов Java или пользовательского метода в случае неполадок при исполнении программы. Таким образом, для програм¬мистов интерес представляет прежде всего тип Exception.
Лучший способ получить представление об исключениях — просмотреть до¬кументацию JDK. Стоит сделать это хотя бы раз, чтобы получить представле¬ние о различных видах исключений, но вскоре вы убедитесь в том, что наиболее принципиальным различием между разными исключениями являются их имена. К тому же количество исключений в Java постоянно растет, и едва ли имеет смысл описывать их в книге. Любая программная библиотека от стороннего про¬изводителя, скорее всего, также будет иметь собственный набор исключений. Здесь важнее понять принцип работы и поступать с исключениями сообразно.
Основной принцип заключается в том, что имя исключения относительно полно объясняет суть возникшей проблемы. Не все исключения определены в пакете java.lang, некоторые из них созданы для поддержки других библиотек, таких как util, net и io, как можно видеть из полных имен их классов или из ба¬зовых классов. Например, все исключения, связанные с вводом/выводом (I/O), унаследованы от java.io.IOException.
Особый случай: RuntimeException
Вспомним первый пример в этой главе:
if(t == null)
throw new NullPointerExceptionO;
Только представьте, как ужасно было бы проверять таким образом каждую ссылку, переданную вашему методу. К счастью, делать это не нужно — такая проверка автоматически выполняется во время исполнения Java-программы, и при попытке использования null-ссылок автоматически возбуждается Null- PointerException. Таким образом, использованная в примере конструкция избы¬точна.
Есть целая группа исключений, принадлежащих к этой категории. Они все¬гда возбуждаются в Java автоматически, и вам не придется включать их в спе¬цификацию исключений. Все они унаследованы от одного базового класса RuntimeException, что дает нам идеальный пример наследования: семейство классов, имеющих общие характеристики и поведение. Вам также не придется создавать спецификацию исключений, указывающую на возбуждение методом RuntimeException (или любого унаследованного от него исключения), так как эти исключения относятся к неконтролируемым (unchecked). Такие исключения означают ошибки в программе, и фактически вам никогда не придется перехва¬тывать их — это делается автоматически. Проверка RuntimeException привела бы к излишнему загромождению программы. И хотя вам обычно не требуется пе¬рехватывать RuntimeException, возможно, вы посчитаете нужным возбуждать не¬которые из них в своих собственных библиотеках программ.
Что же происходит, когда подобные исключения не перехватываются? Так как компилятор не заставляет перечислять такие исключения в спецификациях, можно предположить, что исключение RuntimeException проникнет прямо в ме¬тод mainQ, и не будет перехвачено. Чтобы увидеть все в действии, испытайте следующий цример:
//: 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"):

Report Page