47

47


•       В контейнере List, как и в массиве, объектам назначаются числовые ин­дексы — таким образом, массивы и List являются упорядоченными кон­тейнерами.



•       Используйте ArrayList при частом использовании произвольного доступа к элементам или LinkedList при частом выполнении операций вставки и удаления в середине списка.



•       Поведение очередей и стеков обеспечивается контейнером LinkedList.



•       Контейнер Map связывает с объектом не целочисленный индекс, адругой объект.Контейнеры HashMap оптимизированы для быстрого доступа, а контейнер TreeMap хранит ключи в отсортированном порядке, но усту­пает по скорости HashMap. В контейнере LinkedHashMap элементы хранят­ся в порядке вставки, но хеширование обеспечивает быстрый доступ.



•       В контейнере Set каждый объект может храниться только в одном экзем­пляре. Контейнер HashSet обеспечивает максимальную скорость поиска, а в TreeSet элементы хранятся в отсортированном порядке. В контейнере LinkedHashSet элементы хранятся в порядке вставки.



•       Использовать старые классы Vector, Hashtable и Stack в новом коде не нужно.



Контейнеры Java — необходимый инструмент, которым вы будете постоянно пользоваться в своей повседневной работе; благодаря им ваш код станет более простым, мощным и эффективным. Возможно, на освоение некоторых аспектов контейнеров потребуется время, но вы быстро привыкнете к классам этой биб­лиотеки и начнете использовать их.


О
бработка ошибок и исключения



Один из основополагающих принципов философии Java состоит в том, что «пло¬хо написанная программа не должна запускаться



В идеале ошибки должны обнаруживаться во время компиляции, перед за¬пуском программы. Однако не все ошибки удается выявить в это время. Ос¬тальные проблемы приходйтся решать во время работы программы, с помощью механизма, который позволяет источнику ошибки передать необходимую ин¬формацию о ней получателю — а последний справляется с возникшими трудно¬стями.






Усовершенствованная система восстановления после ошибок входит в чис¬ло важнейших факторов, влияющих на надежность кода. Восстановление осо¬бенно важно в языке Java, на котором часто пишутся программные компоненты, используемые другими сторонами. Надежная система может быть построена только из надежных компонентов. Унифицированная модель передачи инфор¬мации об ошибках в Java позволяет компонентам передавать информацию о возникших проблемах в клиентский код.



Механизм исключений значительно упрощает создание больших надежных программ, уменьшает объем необходимого кода и повышает уверенность в том, что в приложении не будет необработанной ошибки. Освоить работу с исклю¬чениями несложно, и это. одна из языковых возможностей, способных принести немедленную и значительную выгоду в ваших проектах. В этой главе будет по¬казано, как правильно организовать обработку исключений в программе, а так¬же как сгенерировать собственное исключение, если какой-то из ваших методов сталкивается с непредусмотренными трудностями.



Основные исключения



Исключительной ситуацией называется проблема, из-за которой нормальное продолжение работы метода или части программы, выполняющихся в данный момент, становится невозможным. Важно отличать исключительную ситуацию



от «обычных» ошибок, когда в текущем контексте имеется достаточно инфор¬мации для преодоления затруднений. В исключительной ситуации обработать исключение в текущем контексте невозможно, потому что вы не располагаете необходимой информацией. Остается единственный выход — покинуть теку¬щий контекст и передать проблему на более высокий уровень. Именно это и происходит при выдаче исключения.



Простейшим примером является деление. Потенциальное деление на нуль может быть выявлено проверкой соответствующего условия. Но что делать, если знаменатель оказался нулем? Возможно, в контексте текущей задачи из¬вестно, как следует поступить с нулевым знаменателем. Но, если нулевой зна¬менатель возник неожиданно, деление в принципе невозможно, и тогда необхо¬димо возбудить исключение, а не продолжать исполнение программы.



При возбуждении исключения происходит сразу несколько вещей. Во-пер- вых, создается объект, представляющий исключение, — точно так же, как и лю¬бой другой объект в Java (в куче, оператором new). Далее текущий поток испол¬нения (тот самый, где произошла ошибка) останавливается, и ссылка на объект, представляющий исключение, извлекается из текущего контекста. С этого мо¬мента включается механизм обработки исключений, который начинает поиск подходящего места программы для передачи исключения. Таким местом явля¬ется обработчик исключений, который пытается решить возникшую проблему так, чтобы программа могла снова попытаться выполнить проблемную опера¬цию или просто продолжила свое выполнение.



В качестве простого примера выдачи исключения представьте ссылку на объект t. Возможно, полученная вами ссылка не была инициализирована; стоит проверить это обстоятельство, прежде чем вызывать методы с использо¬ванием этой ссылки. Чтобы передать информацию об ошибке на более высо¬кий уровень, создайте объект, представляющий передаваемую информацию, и «запустите» его из текущего контекста. Тем самым вы возбудите исключение. Вот как это выглядит:



if(t — null)



throw new NullPointerException( );



Вырабатывается исключение, которое позволяет вам — в текущем контек¬сте — переложить с себя ответственность, не задумываясь о будущем. Ошибка, словно по волшебству, обрабатывается где-то в другом месте (вскоре мы узнаем, где именно).



Один из основополагающих аспектов исключений состоит в том, что при возникновении нежелательных ситуаций выполнение программы не продолжа¬ется по обычному пути. Исключения позволяют вам (в крайнем случае) остано¬вить программу и сообщить о возникших трудностях или (в идеале) разобрать¬ся с проблемой и вернуть программу в стабильное состояние.



Аргументы исключения



Исключения, как и любые объекты Java, создаются в куче оператором new, ко¬торый выделяет память и вызывает конструктор. У всех стандартных исключе¬ний существует два конструктора: стандартный (по умолчанию) и другой, со строковым аргументом, в котором можно разместить подходящую информа¬цию об исключении:



throw new NullPointerExceptionC't = null");



Переданная строка потом может быть извлечена различными способами, о чем будет рассказано позже.



Ключевое слово throw влечет за собой ряд довольно интересных действий. Как правило, сначала new используется для создания объекта, представляюще¬го условие происшедшей ошибки. Ссылка на указанный объект передается ко¬манде throw. Фактически этот объект «возвращается» методом, несмотря на то что для возвращаемого объекта обычно предусмотрен совсем другой тип. Та¬ким образом, упрощенно можно говорить об обработке исключений как об аль¬тернативном механизме возврата из исполняемого метода (впрочем, с этой ана¬логией не стоит заходить слишком далеко). Возбуждение исключений также позволяет выходить из простых блоков видимости. В обоих случаях возвраща¬ется объект исключения и происходит выход из текущего метода или блока.



Но все сходство с обычным возвратом из метода на этом заканчивается, по¬скольку при возврате из исключения вы попадаете совсем не туда, куда попали бы при нормальном вызове метода. (Обработчик исключения может находить¬ся очень «далеко» — на расстоянии нескольких уровней в стеке вызова — от ме¬тода, где возникла исключительная ситуация.)



Вообще говоря, можно возбудить любой тип исключений, происходящих от объекта Throwable (корневой класс иерархии исключений). Обычно для раз¬ных типов ошибок возбуждаются разные типы исключений. Информация о случившейся ошибке как содержится внутри объекта исключения, так и ука¬зывается косвенно в самом типе этого объекта, чтобы кто-то на более высоком уровне сумел выяснить, как поступить с исключением. (Нередко именно тип объекта исключения является единственно доступной информацией об ошибке, в то время как внутри объекта никакой полезной информации нет.)



Перехват исключений



Чтобы увидеть, как перехватываются ошибки, сначала следует усвоить понятие защищенной секции — той части программы, в которой могут произойти исклю¬чения и за которой следует специальный блок, отвечающий за обработку этих исключений.



Блок try



Если вы «находитесь» внутри метода и инициируете исключение (или это дела¬ет другой вызванный метод), этот метод завершит работу при возникновении исключения. Но если вы не хотите, чтобы оператор throw завершил работу ме¬тода, разместите в методе специальный блок для перехвата исключения — так называемый блок try. Этот блок представляет собой простую область действия, которой предшествует ключевое слово try:



try {



// Фрагмент, способный возбуждать исключения



}



Если бы не обработка исключений, для тщательной проверки ошибок вам пришлось бы добавить к вызову каждого метода дополнительный код для про¬верки ошибок — даже при многократном вызове одного метода. С обработкой исключений весь код размещается в блоке try, который и перехватывает все воз¬можные исключения в одном месте. А это означает, что вашу программу стано¬вится значительно легче писать и читать, поскольку выполняемая задача не сме¬шивается с обработкой ошибок.



Обработчики исключений



Конечно, возбужденное исключение в конечном итоге должно быть где-то обра¬ботано. Этим местом является обработчик исключений, который создается для каждого исключения, которое вы хотите перехватить. Обработчики исключе¬ний размещаются прямо за блоком try и обозначаются ключевым словом catch:



try {



// Часть программы, способная возбуждать исключения } catch(Typel idl) {



// Обработка исключения Typel } catch(Туре2 id2) {



// Обработка исключения Туре2 } catch(ТуреЗ id3) {



// Обработка исключения ТуреЗ



}



//ИТ д.



Каждое предложение catch (обработчик исключения) напоминает малень¬кий метод, принимающий один и только один аргумент определенного типа. Идентификатор (idl, id2 и т. д.) может использоваться внутри обработчика точ¬но так же, как и метод распоряжается своими аргументами. Иногда этот иден¬тификатор остается невостребованным, так как тип исключения дает достаточ¬но информации для его обработки, но тем не менее присутствует он всегда.



Обработчики всегда следуют прямо за блоком try. При возникновении ис¬ключения механизм обработки исключений ищет первый из обработчиков ис¬ключений, аргумент которого соответствует текущему типу исключения. После этого он передает управление в блок catch, и таким образом исключение счита¬ется обработанным. После выполнения предложения catch поиск обработчиков исключения прекращается. Выполняется только одна секция catch, соответст¬вующая типу исключения; в этом отношении обработка исключений отличает¬ся от команды switch, где нужно дописывать break после каждого case, чтобы предотвратить исполнение всех прочих case.



Заметьте также, что внутри блока try могут вызываться различные методы, способные породить одинаковые типы исключения, но обработчик йонадобится всего один.



Прерывание в сравнении с возобновлением



В теории обработки исключений имеется две основные модели. Модель преры¬вания (которое используется в Java и С++) предполагает, что ошибка настолько серьезна, что при возникновении исключения продолжить исполнение невоз¬можно. Кто бы ни возбудил исключение, сам факт его выдачи означает, что ис¬править ситуацию «на месте» невозможно и возвращать управление обратно не нужно.



Альтернативная модель называется возобновлением. Она подразумевает, что обработчик ошибок сделает что-то для исправления ситуации, после чего пред¬принимается попытка повторить неудавшуюся операцию в надежде на успеш¬ный исход. В таком случае исключение больше напоминает вызов метода — чтобы применить модель возобновления в Java, вам придется пойти именно по этому пути (то есть не возбуждать исключение, а вызвать метод, способный решить проблему). Также можно создать блок try внутри цикла while, который станет снова и снова обращаться к этому блоку, пока не будет достигнут нуж¬ный результат.



Исторически сложилось, что программисты, использующие операционные системы с поддержкой возобновления, со временем переходили к модели пре¬рывания, забывая другую модель. Хотя идея возобновления выглядит привле¬кательно, она не настолько полезна на практике. Основная причина кроется в обратной связи: обработчик ошибки часто должен знать, где произошло ис¬ключение и содержать специальный код для каждого отдельного места ошибки. А это усложняет написание и поддержку программ, особенно для больших сис¬тем, где исключения могут быть сгенерированы во многих различных местах.

Report Page