И.Г. Головин, И.А. Волкова - Языки и методы программирования (1160773), страница 30
Текст из файла (страница 30)
Действительно, ожидать, что неспецифицированное исключение будет обработано, не следует (ведь никто и не подозревал, чтооно произойдет), а нарушение спецификации говорит о серьезнойошибке в функции, так что лучше завершить программу сразу, чемпродолжать распространение ИС, потеряв информацию о месте возникновения реальной проблемы.Язык Java тоже содержит конструкцию спецификации ИС, толькоэта конструкция обязательна.Это означает, что функция ignore из предыдущего примера небудет успешно откомпилирована:class X extends Exception { ... }class Sample{void f throws X { ....
throw new X ( ) ; .... }void suppress (){// нет списка — значит ничего не выбрасываетtry{f(); // здесь может возникнуть ИС типа X}catch (X ex){// успешная обработка ИС типа X}void ignore ()152{f (); / / ошибка: необработанное исключение X}}Наличие обязательной спецификации ИС повышает надежностьпрограмм на Java, заставляя программиста тщательнее продумыватьвопросы обработки ошибок. Однако с языковой точки зрения обязательность ИС породила некоторую проблему.Проблема возникает вследствие того, что существуют ИС, появление которых возможно всюду. Это относится к стандартнымИС, а точнее, к ИС, которые возбуждаются исполняющей системойвиртуальной Java-машины (JRTE).Исключения, порождаемые JRTE, относятся к одному из двухклассов (точнее, являются производными от этих классов): Errorи RuntimeException.
Все ИС, порождаемые JRTE, не надо специфицировать. Рассмотрим подробнее иерархию классов ИС.class Error extends Throwable {...} — ошибки функционирования Java-машины и JRTE;class Exception extends Throwable { . . .} — базовыйкласс для пользовательских ИСclass RuntimeException extends Exception {...} —базовый класс для ошибок типа выхода за границу индекса массива, нехватки памяти и так далее.Очевидно, что ошибки типа Error могут непредсказуемо возникать везде, и обрабатывать их не надо. Как можно исправить наJava ошибки работы JVM?Ошибки типа RuntimeException могут появляться тоже почтивезде (например, везде, где порождаются новые объекты, и т.д.).Требование повсеместной проверки таких ошибок невозможно.
Однако эти ошибки отличаются от первой категории (и потому имеютобщий базовый класс с пользовательскими ИС) тем, что программистможет по своему усмотрению ставить ловушки на отдельные ИС изэтого класса. При этом компилятор не будет требовать обязательнойспецификации или реакции на эти ИС.Таким образом, обязательно специфицировать или обрабатывать (подавлять) только ИС, являющиеся наследниками классаException, но не RuntimeException. Именно к этому виду должныотноситься пользовательские ИС.Конечно, программист может «обмануть» транслятор (точнее,самого себя), выведя свои ИС из Error или RuntimeException.
Авторы языка Java указывают: «Расширение типов RuntimeExceptionили Error и использование их объектов в методах конкретных классов свидетельствует о неверном понимании механизмов обработкиисключений и затрудняет практическое применение методов про153граммистами, которые вправе надеяться на наличие исчерпывающегоописания набора возможных исключений в составе предложенийthrows» [2].Конструкция t r y - f i n a l 1 уКак уже отмечалось, в процессе свертки стека в языке C++ гарантируется выполнение деструкторов всех локальных объектов.Если программист грамотно использует методики типа RAII (см.подразд.
7.2), то он может быть уверен, что локально захваченныересурсы будут освобождены.Однако в языках с автоматической сборкой мусора уничтожениеобъектов (и, как следствие, выполнение финализаторов) носит недетерминированный характер. Существуют классы, объекты которыхнеобходимо уничтожать (освобождая захваченные ими ресурсы) позапросу (например, классы для работы с файлами и т.п.).
Работас такими объектами проходит по следующей схеме:создание объекта;работа с объектом;уничтожение объекта;Однако если в процессе работы с объектом произойдет выброснеобработанной ИС, то объект уничтожен не будет, и произойдетутечка ресурса.Необходимо иметь механизм гарантированного исполнения некоторого кода в блоке независимо от того, завершился блок нормальноили аварийно.Такой механизм уже есть в C++ (деструкторы локальных объектов),а в C# и Java эту роль играет конструкция t r y - f i n a l l y :try {блок кода}finally {// этот код выполнится сразу после любого// выхода из try-блока}Блок finally может появляться также после всех ловушек tryблока, если они есть.
Тогда работа с явно уничтожаемым объектомможет выполняться по следующей схеме:создание-объекта ;try{работа-с-объектом;154}ловушкиfinally{уничтожение-объекта;}В языке C# есть специальный интерфейс для работы с явно уничтожаемыми объектами:interface IDisposable{void D i s pose();}Явно уничтожаемые объекты должны реализовать этот интерфейс.Процедура Dispose () должна освобождать ресурсы, занятые объектом (кроме памяти, разумеется, поскольку это забота сборщикамусора).Примером класса, реализующего этот интерфейс, является стандартный класс Image из библиотеки .NET, инкапсулирующий работус растровыми изображениями. Схема работы с объектами типа Imageследующая:Image img = Image.FromFile("picture.jpg");try{работа-с-картинкой-в-img}finally{((IDisposable)img).Disp o s e (); // освободили// картинку}Интерфейс IDisposable является примером интерфейса дляязыковой интеграции.
Этот интерфейс интегрирован с u s i n g блоком языка С#. Оператор using-блок позволяет автоматическидля заданной переменной (типа ссылки на объект, реализующийIDisposable) гарантированно выполнить процедуру Dispose ().В примере using-блока выполняется та же работа, что и в предыдущем примере:using (Image img = Image.FromFile("picture.jpg") ){работа-с-картинкой-в-img}// img гарантированно освобожденГ л а в а 10СТАТИЧЕСКАЯ ПАРАМЕТРИЗАЦИЯ. ПОНЯТИЕОБ ОБОБЩЕННОМ ПРОГРАММИРОВАНИИ10.1. Параметрический полиморфизмСовременные объектно-ориентированные языки поддерживаюттри вида полиморфизма: статический, динамический и параметрический.Статический полиморфизм в форме перегрузки подпрограмм и динамический полиморфизм в форме динамического связывания виртуальных методов поддерживаются всеми объектно-ориентированнымиязыками.
Параметрический полиморфизм в форме механизма шаблонов реализован в языке C++ с 1990-х гг., а в языках C# и Java —относительно недавно (2003 —2005 гг.).Первые две формы полиморфизма мы рассмотрели ранее (см.гл. 6, 7 и 8). Отличительной чертой этих видов полиморфизма является то, что связывание сводится к выбору одного варианта изнекоторого конечного набора. При статическом полиморфизме попрофилю вызова подпрограммы статически выбирается вариант ееперегрузки, а при динамическом — в момент вызова виртуальногометода по ссылке (указателю) динамически выбирается вариант замещения этого метода.Отличие параметрического полиморфизма состоит в том, что присвязывании может порождаться новая сущность — класс или функция (метод).
Методы реализации параметрического полиморфизмадостаточно сильно различаются для разных языков.При параметрическом полиморфизме полиморфными сущностямиявляются параметризованные типы (классы) и подпрограммы (методы). В C++ параметризованные сущности называются шаблонамиклассов и функций, а в C# и Java — обобщенными (или настраиваемыми) классами и методами, или обобщениями.Два основных понятия параметрического полиморфизма — этообъявление параметризованной абстракции (шаблона или обобщения) и конкретизация этой абстракции (т. е. ее использование).Связывание конкретизации и объявления всегда происходит статически (во время трансляции).В C++ при конкретизации шаблона происходит создание новыхнепараметризованных сущностей — классов и функций (конечно,повторного создания одних и тех же сущностей не происходит, транслятор за этим следит).
Во время конкретизации происходит статиче156ский контроль корректности (соответствия типов) и генерация кода.Конкретизация происходит статически во время трансляции.В C# при конкретизации обобщения проверяется корректность(соответствие типов). Генерация машинного кода для создаваемыхнепараметризованных сущностей происходит при запуске сборки(JIT-компилятором среды .NET) на основе промежуточного кода,созданного при трансляции объявления обобщения, и информациио типах — аргументах конкретизации.И, наконец, в языке Java при конкретизации обобщения толькопроверяется корректность конкретизации (соответствие типов).Можно рассматривать конкретизацию как процесс создания новых непараметризованных сущностей (классов и методов), однаковся информация об этих сущностях существует только во времятрансляции и стирается при генерации объектного кода для JVM.Фактически во всех конкретизациях используется одна версия обобщенного класса или метода, код которого генерируется в контекстеобъявления.Каждый из этих подходов обладает своими достоинствами и недостатками.
Механизм шаблонов C++ позволяет создавать оченьмощные и одновременно эффективные абстракции. Выразительныесредства языка C++ не имеют аналогов среди современных индустриальных языков. На основе статической параметризации разработаныбиблиотеки шаблонов STL (часть стандартной библиотеки C++), Lokiи Boost, значительная часть которой вошла в последний стандартC++.Правда, платой за мощность и выразительность при этом сталаповышенная сложность шаблонов C++.Обобщения в языке C# позволяют создавать мощные и достаточноэффективные параметризованные библиотеки, распространяемыев виде сборок.Обобщения в языке Java также позволяют создавать удобныепараметризованные классы, однако выигрыша в эффективности посравнению с непараметризованным представлением в этом случаенет.