Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Джош Блох

.pdf
Скачиваний:
57
Добавлен:
08.03.2016
Размер:
27.13 Mб
Скачать

Предисловие автора к первой редакции

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

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

Купертино, ш т. Калифорния

Апрель 2001 г.

XXIV

Г л а в а

Введение

^гга книга писалась с тем, чтобы помочь вам наиболее эффективно использовать язык программирования Java и его основные библиоте­ ки java, lang, java, util и, в меньшей степени, java. util, concurrent и j ava. io. Время от времени в книге затрагиваются и другие библио­ теки, но мы не касаемся программирования графического интерфейса пользователя, специализированных A PI или мобильных устройств.

Книга состоит из семидесяти восьми статей, каждая из которых описывает одно правило. В этих статьях собран опыт, который самые лучшие и опытные программисты обычно считают полезным. Ста­ тьи произвольно разбиты на десять глав, каждая из которых касает­ ся того или иного обширного аспекта проектирования программного обеспечения. Нет необходимости читать эту книгу от корки до корки: каждая статья в той или иной степени самостоятельна. Статьи име­ ют множество перекрестных ссылок, поэтому вы можете с легкостью построить по этой книге ваш собственный учебный курс.

Многие новые возможности были добавлены в платформу Java 5 (релиз 1.5). Большинство статей этой книги в той или иной степени используют эти новые возможности. Следующая таблица иллюстри­ рует, где и какие новые возможности были освещены в данной книге:

Большинство статей иллюстрируются примерами программ. Главной особенностью этой книги является наличие в ней примеров

1

Java. util.concurrent
Статический импорт
А ргументы переменной длины V arargs
А втоупаковка
Ц иклы for-each
Аннотации
Перечисления
О бщ ности
Новая возможность
С татьи
6 8 , 6 9
С татья 19
С татья 4 2
С татьи 4 0 , 4 9
С татья 4 6
С татьи 3 3 —37
С татьи 3 0 —34
Глава 5
Diaea или
статья, где она описана

Глава 1 Введение

программного кода, кото­ рые иллюстрируют многие шаблоны (design pattern)

и идиомы. Где это необхо­ димо, шаблоны и идиомы имеют ссылки на основ­ ные работы в этой области [Gamma95].

Многие статьи содер­ жат один или несколько примеров программ, ил­ люстрирующих приемы, которых следует избегать. Подобные примеры, ино­ гда называемые «антиша­ блонами», четко обозначе­

ны комментарием, таким как «Никогда так не делайте! ». В каждом таком случае в статье дается объяснение, почему этот пример плох, и предлагается альтернатива.

Эта книга не предназначена для начинающих: предполагается, что вы уже хорошо владеете языком программирования Java. Если же это не так, обратитесь к одному из множества замечательных вводных тек­ стов [Amold05, Sestoft05]. Хотя эта книга построена так, чтобы она была доступна для любого, кто работает с этим язьжом, она должна давать пищу для размышлений даже опытным программистам.

Большинство правил этой книги берут начало от нескольких фун­ даментальных принципов. Ясность и простота имеют первостепенное значение. Функционирование модуля не должно вызывать удивле­ ние у его пользователя. Модули должны быть настолько компакт­ ны, насколько это возможно, но не более того. (В этой книге термин «модуль» относится к любому программному компоненту, который используется много раз, от отдельного метода до сложной системы, состоящей из нескольких пакетов.) Программный код следует ис­ пользовать повторно, а не копировать. Взаимозависимость между модулями должна быть сведена к минимуму. Ошибку нужно выяв-

Глава 1 • Введение

лять как можно ближе к тому месту, где она возникла, в идеале — уже на стадии компиляции.

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

Большая часть этой книги посвящена отнюдь не производи­ тельности программ. Речь идет о написании понятных, правильных, полезных, надежных, гибких программ, которые удобно сопрово­ ждать. Если вы сможете сделать это, то добиться необходимой про­ изводительности программ будет относительно просто (статья 55). В некоторых статьях обсуждаются вопросы производительности, а в нескольких даже приведены показатели производительности. Эти данные, предваряемые выражением «на моей машине», в луч­ шем случае следует рассматривать как приблизительные.

Для справки, моя машина — это старый компьютер домашней сборки с процессором 2.2 ГГц двухъядерный Opteron 170 с 2 Гбайт оперативной памяти под управлением Microsoft Windows Х Р Professional SP2, на котором установлен Java 1.6_05 Standard Edition Software Development Kit (SD K ) компании Sun. В состав этого S D K входят две виртуальные машины — Java HotSpot Client V M (кли­ ентская виртуальная машина) и Server V M (серверная виртуальная машина). Производительность измерялась на серверной машине

При обсуждении особенностей языка программирования Java и его библиотек иногда возникает необходимость сослаться на кон­ кретные версии. Для краткости в этой книге используются «рабо­ чие», а не официальные номера версий. В таблице 1.1 показано соот­ ветствие между названиями версий и их рабочими номерами.

Данные примеры по возможности являются полными, однако предпочтение отдается не завершенности, а удобству чтения. В при­ мерах широко используются классы пакетов java.util и java.io. Соот-

3

Глава 1 Введение

Официальное

Рабочий

название

номер

версии

версии

J D K

1 .1 .x / J R E 1.1.x

и

Java 2

Platform ,

1.2

Standard Edition, v 1.2

 

Java 2

Platform ,

1.3

Standard Edition, v 1.3

 

пример, вам потребуется добавить один или более операторов import:

import java.util.*;

import java.util.concurrent*;

import java.io.*;

В примерах опущены другие детали. На веб-сайте этой книги

Java 2

Platform ,

1.4

(http://j ava.sun.com/docs/books/

Standard Edition, v 1.4

 

effective) содержится

полная

Java 2

Platform ,

1.5

версия каждого примера, которую

Standard Edition, v 5 .0

 

можно откомпилировать

и запу­

Java 2

Platform ,

1.6

стить.

 

Standard Edition, v 6 .0

Технические термины в этой

 

книге большей частью использу­

ются в том виде, как они были определены в The Java Language Specification [JLS]. Однако некоторые термины заслуживают от­ дельного упоминания. Язык Java поддерживает четыре группы ти­ пов: интерфейсы (interface) (в том числе и аннотации {annotations)), классы (class) (в том числе и перечисления (enums)), массивы (array) и простые типы (primitive). Первые три группы называ­ ются ссылочными типами (reference type). Экземплярами классов и массивов являются объекты, значения простых типов таковыми не являются. Членами класса (members) являются его поля (fields), методы (methods), а также классы-члены (member classes) и ин­ терфейсы-члены (member interfaces). Сигнатура метода (signature)

состоит из его названия и типов, которые имеют его формальные параметры. Тип значения, которое возвращается этим методом, в сигнатуру не входит.

Некоторые термины в этой книге используются в ином значе­ нии, чем в Thefava Language Specification.

4

Глава 1 • Введение

Вотличие от указанной спецификации наследование (inheritance)

вэтой книге используется как синоним образования подклассов (subclassing). Вместо того чтобы использовать для интерфейсов тер­ мин «наследование», в этой книге просто констатируется, что некий класс реализует (implement) интерфейс или что один интерфейс явля­ ется расширением другого (extend). Чтобы описать уровень доступа, который используется, когда ничего больше не указано, в книге ис­ пользуется описательный термин «доступ только в пределах пакета» (package-private) вместо формально правильного термина «доступ по умолчанию» (default access ) [JLS, 6.6.1].

Вэтой книге используются несколько технических терминов, ко­ торых нет в Thejava Language Specification. Термин «внешний A PI»

(exported A PI), или просто API, относится к классам, интерфей­ сам, конструкторам, членам и серийным формам, с помощью кото­ рых программист получает доступ к классу, интерфейсу или пакету. (Термин API, являющийся сокращением от application programming interface — программный интерфейс приложения, используется вме­ сто термина «интерфейс» (interface), который следовало бы исполь­ зовать в противном случае. Это позволяет избежать путаницы с од­ ноименной конструкцией языка Java.) Программист, который пишет программу, использующую некий API, называется здесь пользова­ телем (user) указанного API. Класс, в реализации которого исполь­ зуется некий API, называется клиентом (client) этого API.

Классы, интерфейсы, конструкторы, члены и серийные формы все вместе называются элементами API (A PI element). Внешний API образуется из элементов API, которые доступны за предела­ ми пакета, где этот API был определен. Указанные элементы может использовать любой клиент, а автор этого API берет на себя их под­ держку. Не случайно документацию именно к этим элементам гене­ рирует утилита Javadoc, будучи запущена в режиме по умолчанию. Грубо говоря, внешний API пакета состоит из открытых (public) и защищенных (protected) членов, а также конструкторов всех от­ крытых классов и интерфейсов в пакете.

5

Г л а в а

_ _ _ _ _ _ _ ------------

я---

.;■■■■■-

■■■■■;-у '-

~

^

(

Создание и уничтожение

объектов

| В этой главе речь идет о создании и уничтожении объектов: как и когда создавать объекты, как и когда этого не делать, как сделать так, чтобы объекты гарантированно уничтожались своевременно, а также как управлять всеми операциями по очистке, которые долж­ ны предшествовать уничтожению объекта.

Рассмотрите возможность замены конструкторов статическими методами генерации

Обычно, чтобы разрешить клиенту получать экземпляр класса, ему предоставляется открытый (public) конструктор. Есть и другой, менее известный прием, который должен быть в арсенале любого программиста. Класс может иметь открытый статический метод генерации (static factory method), который является просто статиче­ ским методом, возвращающим экземпляр класса. Простой пример такого метода возьмем из класса Boolean (класса, являющего обо­ лочкой для простого типа boolean). Этот метод преобразует простое значение boolean в ссылку на объект Boolean:

6

С татья 1

public static Boolean valueOf(boolean b) { return (b ? Boolean.TRUE : Boolean.FALSE);

}

Обратите внимание, что статический метод генерации и порож­ дающий шаблон из Шаблонов проектирования (Gamma95, с. 107) не есть одно и то же

Статические методы генерации могут быть предоставлены кли­ ентам класса не только вместо конструкторов, но и в дополнение к ним. Замена открытого конструктора статическим методом генера­ ции имеет как достоинства, так и недостатки.

Первое преимущество статического метода генерации состо­ ит в том, что, в отличие от конструкторов, он имеет название.

Тогда как параметры конструктора сами по себе не дают описания возвращаемого объекта, статический метод генерации с хорошо по­ добранным названием может упростить работу с классом и, как след­ ствие, сделать соответствующий программный код клиента более понятным. Например, конструктор Biglnteger(int, int, Random), который возвращает Biglnteger, вероятно являющийся простым чис­ лом (prime), лучше было бы представить как статический метод гене­ рации с названием Biglnteger. probablePrime. конечном счете этот статический метод был добавлен в версии 1.4.)

Класс может иметь только один конструктор с заданной сигнату­ рой. Известно, что программисты обходят это ограничение, создавая конструкторы, чьи списки параметров отличаются лишь порядком следования типов. Это плохая идея. Человек, использующий подоб­ ный A PI, не сможет запомнить, для чего нужен один конструктор, а для чего другой, и в конце концов по ошибке вызовет не тот кон­ структор. Люди, читающие программный код, в котором использу­ ются такие конструкторы, не смогут понять, что же он делает, если не будут сверяться с сопроводительной документацией к этому классу.

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

7

Глава 2 Создание и уничтожение объектов

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

Второе преимущество статических методов генерации за ­ ключается в том, что, в отличие от конструкторов, они не обя­ заны при каждом вызове создавать новый объект. Это позволяет использовать для неизменяемого класса (статья 15) предварительно созданные экземпляры либо кэшировать экземпляры класса по мере их создания, а затем раздавать их повторно, избегая создания ненуж­ ных дублирующих объектов. Подобный прием иллюстрирует метод Boolean. valueOf (boolean): он не создает объектов. Эта методика схо­ жа с шаблоном Flyweight (Gamma95, с. 195). Она может значи­ тельно повысить производительность программы, если в ней часто возникает необходимость в создании одинаковых объектов, особенно в тех случаях, когда создание этих объектов требует больших затрат.

Способность статических методов генерации при повторных вы­ зовах возвращать тот же самый объект позволяет классам в любой момент времени четко контролировать, какие экземпляры объекта еще существуют. Классы, которые делают это, называются классами

контроля экземпляров (instance-controlled). Есть несколько причин для написания таких классов. Во-первых, контроль над экземплярами позволяет дать гарантию, что некий класс является синглтоном (ста­ тья 3) или что он является абстрактным (статья 4). Во-вторых, это позволяет убедиться в том, что у неизменяемого класса (статья 5) не появилось двух одинаковых экземпляров: a.equals(b) тогда и толь­ ко тогда, когда а==Ь. Если класс дает такую гарантию, его клиенты вместо метода equals (Object) могут использовать оператор ==, что может привести к существенному повышению производительности программы. Перечисления типов (статья 30) также дают такую га­ рантию.

Третье преимущество статического метода генерации заклю­ чается в том, что, в отличие от конструктора, он может возвра­

8

С татья 1

тить объект, который соответствует не только заявленному типу возвращаемого значения, но и любому его подтипу. Это дает вам значительную гибкость в выборе класса для возвращаемого объекта. Например, благодаря такой гибкости интерфейс A P I может возвра­ щать объект, не декларируя его класс как public. Сокрытие реали­ зации классов может привести к созданию очень компактного API. Этот прием идеально подходит для конструкций, построенных на интерфейсах (статья 18), когда эти интерфейсы для статических ме­ тодов генерации задают собственный тип возвращаемого значения. У интерфейсов не может быть статических методов, так что статические методы интерфейса с именем Туре помещаются в абстрактный класс (статья 4) с именем Types, для которого нельзя создать экземпляр.

Например, архитектура Collections Framework имеет тридцать две полезные реализации интерфейсов коллекции: неизменяемые коллекции, синхронизированные коллекции и т.д. Большинство этих реализаций с помощью статических методов генерации сводятся в единственный класс (java.util.Collections), для которого невоз­ можно создать экземпляр. Все классы, соответствующие возвращае­ мым объектам, не являются открытыми.

A PI Collections Framework имеет гораздо меньшие размеры, чем это было бы, если бы в нем были представлены тридцать два отдель­ ных открытых класса для всех возможных реализаций. Сократил­ ся не просто объем этого API, но и его «концептуальная нагрузка». Пользователь знает, что возвращаемый объект имеет в точности тот API, который указан в соответствующем интерфейсе, и ему нет нуж­ ды читать дополнительные документы к этому классу. Более того, использование такого статического метода генерации дает клиенту право обращаться к возвращаемому объекту, используя его собствен­ ный интерфейс, а не через интерфейс класса реализации, что обычно является хорошим приемом (статья 52).

Скрытым может быть не только класс объекта, возвращаемого открытым статическим методом генерации. Сам этот класс может ме­ няться от вызова к вызову, в зависимости от того, какие значения па-

9

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]