В С++ спецификация исключения не входит в информацию о типе функ¬ции. Единственная проверка, осуществляемая во время компиляции, относится к согласованному использованию исключений: к примеру, если функция или метод возбуждает исключения, то перегруженная или переопределенная версия должна возбуждать те же самые исключения. Однако, в отличие от Java, компи¬лятор не проверяет, действительно ли функция или метод возбуждают данное исключение, или полноту спецификации (то есть описывает ли она все исклю¬чения, возможные для этого метода). Если возбуждается исключение, не входя¬щее в спецификацию, программа на С++ вызывает функцию unexpected() из стандартной библиотеки.
Интересно отметить, что из-за использования шаблонов (templates) специ¬фикации исключений отсутствуют в стандартной библиотеке С++. В Java суще¬ствуют ограничения на использование параметризованных типов со специфи¬кациями исключений.
Перспективы
Во-первых, язык Java, по сути, стал первопроходцем в использовании контро¬лируемых исключений (несомненно из-за спецификаций исключений С++ и того факта, что программисты на С++ не уделяли им слишком много внима¬ния). Это был эксперимент, повторить который с тех пор пока не решился еще ни один язык.
Во-вторых, контролируемые исключения однозначно хороши при рассмот¬рении вводных примеров и в небольших программах. Оказывается, что трудно¬уловимые проблемы начинают проявляться при разрастании программы. Ко¬нечно, программы не разрастаются тут же и сразу, но они имеют тенденцию расти незаметно. И когда языки, не предназначенные для больших проектов, используются для небольших, но растущих проектов, мы в некоторый момент с удивлением обнаруживаем, что ситуация изменилась с управляемой на за¬труднительную в управлении. Именно это, как я полагаю, может произойти, ко¬гда проверок типов слишком много, и особенно в отношении контролируемых исключений.
Одним из важных достижений Java стала унификация модели передачи ин¬формации об ошибках, так как обо всех ошибках сообщается посредством ис¬ключений. В С++ этого не было, из-за обратной совместимости с С и возмож¬ности задействовать старую модель простого игнорирования ошибок. Когда Java изменил модель С++ так, что сообщать об ошибках стало возможно только посредством исключений, необходимость в дополнительных мерах принуждения в виде контролируемых исключений сократилась.
В прошлом я твердо считал, что для разработки надежных программ необхо¬димы и контролируемые исключения, и строгая статическая проверка типов. Однако опыт, полученный лично и со стороны1, с языками, более динамичны¬ми, чем статичными, привел меня к мысли, что на самом деле главные преиму¬щества обусловлены следующими аспектами:
1. Унификация модели сообщения об ошибках посредством исключений (независимо от того, заставляет ли компилятор программиста их обраба¬тывать).
2. Проверка типов, не привязанная к тому, когда она проводится — на ста¬дии компиляции или во время работы программы.
Вдобавок снижение ограничений времени компиляции весьма положитель¬но отражается на продуктивности программиста. С другой стороны, для ком¬пенсации чрезмерной жесткости статической проверки типов необходимы реф¬лексия и параметризация, как вы убедитесь в некоторых примерах книги.
Некоторые уверяли меня, что все сказанное является кощунством, безна¬дежно испортит мою репутацию, приведет к гибели цивилизации и провалу большой доли программных проектов. Вера в то, что выявление ошибок на ста¬дии компиляции спасет ваш проект, весьма сильна, но гораздо важнее созна¬вать ограничения того, на что способен компьютер. Стоит помнить:
«Хороший язык программирования помогает программистам писать хоро¬шие программы. Ни один из языков программирования не может запретить сво¬им пользователям писать плохие программы ».
В любом случае исчезновение когда-либо из Java контролируемых исключе¬ний весьма маловероятно. Это слишком радикальное изменение языка, и защит¬ники их в Sun весьма сильны. История Sun неотделима от политики абсолютной обратной совместимости — фактически любое программное обеспечение Sun ра¬ботает на любом оборудовании Sun, как бы старо оно ни было. Но, если вы чув¬ствуете, что контролируемые исключения становятся для вас препятствием (особенно если вас заставляют обрабатывать исключение, а вы не знаете, как с ним поступить), существует несколько вариантов.
Передача исключений на консоль
В несложных программах, как во многих примерах данной книги, простейшим решением является передача исключения за пределы метода main(), на консоль. К примеру, при открытии файла для чтения (подробности вы вскоре узнаете) необходимо открыть и закрыть поток FilelnputStream, который возбуждает ис¬ключения. В небольшой программе можно поступить следующим образом (по¬добный подход характерен для многих примеров книги):
//• excepti ons/Mai nExcepti on.java
import java io *;
public class MainException {
// Передаем все исключения на консоль, public static void main(String[] args) throws Exception { // Открываем файл: FilelnputStream file =
new FilelnputStreamC'MainException.java");
// Используем файл // Закрываем файл- file.closeO.
}
} ///:-
Заметьте, что main() — такой же метод, как и все прочие; он тоже может иметь спецификацию исключений, и здесь типом исключения является Excep¬tion, базовый класс всех контролируемых исключений. Передавая его на кон¬соль, вы освобождаетесь от необходимости написания предложений try-catch в теле метода main().
Преобразование контролируемых исключений в неконтролируемые
Рассмотренный выше подход хорош при написании метода main(), но в более общих ситуациях не слишком полезен. Подлинная проблема возникает при на¬писании тела самого обычного метода, когда при вызове другого метода вы чет¬ко сознаете: «Понятия не имею, что делать с исключением дальше, но „съедать" мне его не хочется, так же как и печатать банальное сообщение». Проблема ре¬шается при помощи цепочек исключений. Управляемое исключение просто «заворачивается» в класс RuntimeException примерно так:
try {
// .. делаем что-нибудь полезное } са!сИ(НеЗнаюЧтоДелатьСЭтимКонтролируемымИсключением е) { throw new RuntimeException(e);
}
Решение идеально подходит для тех случаев, когда вы хотите «подавить» контролируемое исключение: вы не «съедаете» его, вам не приходится описы¬вать его в своей спецификации исключений, и благодаря цепочке исключений вы не теряете информацию об исходном исключении.
Описанная методика позволяет игнорировать исключение и пустить его «всплывать» вверх по стеку вызова без необходимости писать блоки try-catch и (или) спецификации исключения. Впрочем, при этом вы все равно можете пе¬рехватить и обработать конкретное исключение, используя метод getCause(), как показано ниже:
// exceptions/TurnOffChecking.java // "Подавление" контролируемых исключений, import java io *;
import static net mindview.util.Print.*;
class WrapCheckedException {
void throwRuntimeException(int type) { try {
switch(type) {
case 0: throw new FileNotFoundExceptionO; case 1: throw new IOExceptionO; case 2- throw new RuntimeExceptionCTfle Я?"). default: return;
}
} catch(Exception e) {
// Превращаем в неконтролируемое: throw new RuntimeException(e);
}
}
}
class SomeOtherException extends Exception {}
public class TurnOffChecking {
public static void main(String[] args) {
WrapCheckedException wee = new WrapCheckedExceptionO: // Можно вызвать throwRuntimeExceptionO без блока try, // и позволить исключению RuntimeException покинуть метод- wee. throwRuntimeExceptionO); // Или перехватить исключение: for (int i =0; i <4; i++) try {
if(i < 3)
wee throwRuntimeException(i);