Джош Блох
.pdfПредисловие автора к первой редакции
(refactoring). Основной задачей перестроения является усовершен ствование структуры системы, а также исключение дублирующего программного кода. Этой цели невозможно достичь, если у компо нент системы нет хорошо спроектированного API.
Ни один язык не идеален, но некоторые — великолепны. Я об наружил, что язык программирования Java и его библиотеки в огром ной степени способствуют повышению качества и производительно сти труда, а также доставляют радость при работе с ними. Надеюсь, эта книга отражает мой энтузиазм и способна сделать вашу работу с языком Java более эффективной и приятной.
Купертино, ш т. Калифорния
Апрель 2001 г.
XXIV
Г л а в а
Введение
^гга книга писалась с тем, чтобы помочь вам наиболее эффективно использовать язык программирования Java и его основные библиоте ки java, lang, java, util и, в меньшей степени, java. util, concurrent и j ava. io. Время от времени в книге затрагиваются и другие библио теки, но мы не касаемся программирования графического интерфейса пользователя, специализированных A PI или мобильных устройств.
Книга состоит из семидесяти восьми статей, каждая из которых описывает одно правило. В этих статьях собран опыт, который самые лучшие и опытные программисты обычно считают полезным. Ста тьи произвольно разбиты на десять глав, каждая из которых касает ся того или иного обширного аспекта проектирования программного обеспечения. Нет необходимости читать эту книгу от корки до корки: каждая статья в той или иной степени самостоятельна. Статьи име ют множество перекрестных ссылок, поэтому вы можете с легкостью построить по этой книге ваш собственный учебный курс.
Многие новые возможности были добавлены в платформу Java 5 (релиз 1.5). Большинство статей этой книги в той или иной степени используют эти новые возможности. Следующая таблица иллюстри рует, где и какие новые возможности были освещены в данной книге:
Большинство статей иллюстрируются примерами программ. Главной особенностью этой книги является наличие в ней примеров
1
Глава 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