Выбрать главу

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