Для возвращаемого значения метод 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 выполняется всегда.