Java: эффективное программирование [Джошуа Блох] (pdf) читать онлайн

Книга в формате pdf! Изображения и текст могут не отображаться!


 [Настройки текста]  [Cбросить фильтры]

Java
ЭФФЕКТИВНОЕ
ПРОГРАММИРОВАНИЕ
Третье издание

Ещё больше книг по Java в нашем телеграм канале:
https://t.me/javalib

Effective Java
Third Edition

Joshua Bloch

/▼Addison-Wesley
Boston • Columbus • Indianapolis • New York • San Francisco • Amsterdam • Cape Town
Dubai • London • Madrid • Milan • Munich • Paris • Montreal • Toronto • Delhi • Mexico City
Sao Paulo • Sydney • Hong Kong • Seoul • Singapore • Taipei • Tokyo

Java
ЭФФЕКТИВНОЕ
ПРОГРАММИРОВАНИЕ
Третье издание

Джошуа Блох

Ещё больше книг по Java в нашем телеграм канале:
https://t.me/javalib

Москва • Санкт-Петербург
2019

ББК 32.973.26-018.2.75
Б70
УДК 681.3.07
ООО “Диалектика"

Зав. редакцией С.Н. Тригуб
Перевод с английского и редакция канд. техн, наук И,В. Красикова
Рецензент канд. физ.-мат. наукД.Е. Намиот
По общим вопросам обращайтесь в издательство “Диалектика" по адресу:
info@dialektika.com, http://www.dialektika.com

Блох, Джошуа
Б70

Java: эффективное программирование, 3-е изд. : Пер. с англ. — СПб. : ООО “Диалектика”,
2019. — 464 с.: ил. — Парал. тит. англ.
ISBN 978-5-6041394-4-8 (рус.)

ББК 32.973.26-018.2.75
Все названия программных продуктов являются зарегистрированными торговыми марками
соответствующих фирм.
Никакая часть настоящего издания ни в каких целях не может быть воспроизведена в какой бы то
ни было форме и какими бы то ни было средствами, будь то электронные или механические, включая
фотокопирование и запись на магнитный носитель, если на это нет письменного разрешения издатель­
ства Addison-Wesley Publishing Company, Inc.
Copyright © 2019 by Dialektika Computer Publishing Ltd.
Authorized translation from the English language edition of Effective Java, 3rd Edition (ISBN 978-0-13468599-1) published by Addison-Wesley Publishing Company, Inc., Copyright © 2018 Pearson Education Inc.
Portions copyright © 2001-2008 Oracle and/or its affiliates.
This translation is published and sold by permission of Pearson Education, Inc., which owns or controls
all rights to sell the same.
All rights reserved. No part of this book may be reproduced or transmitted in any form or by any means,
electronic or mechanical, including photocopying, recording, or by any information storage or retrieval sys­
tem, without the prior written permission of the copyright owner and the Publisher.

Научно-популярное издание
Джошуа Блох

Java: эффективное программирование, 3-е издание
Подписано в печать 31.10.2018.
Формат 70x100/16. Гарнитура Times.
Усл. печ. л. 29,0. Уч.-изд. л. 23,4.
Тираж 500 экз. Заказ № 11008.
Отпечатано в АО “Первая Образцовая типография”
Филиал “Чеховский Печатный Двор”
142300, Московская область, г. Чехов, ул. Полиграфистов, д. 1
Сайт: www.chpd.ru, E-mail: sales@chpd.ru, тел. 8 (499) 270-73-59
ООО “Диалектика”, 195027, Санкт-Петербург, Магнитогорская ул., д. 30, лит. А, пом. 848

ISBN 978-5-6041394-4-8 (рус.)
ISBN 978-0-13-468599-1 (англ.)

© ООО “Диалектика”, 2019
© Pearson Education Inc., 2018

Оглавление
Вступительное слово

13

Предисловие

15

Благодарности

19

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

25

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

29

Глава 3. Методы, общие для всех объектов

67

Глава 4. Классы и интерфейсы

109

Глава 5. Обобщенное программирование

159

Глава 6. Перечисления и аннотации

203

Глава 7. Лямбда-выражения и потоки

245

Глава 8. Методы

283

Глава 9. Общие вопросы программирования

321

Глава 10. Исключения

359

Глава 11. Параллельные вычисления

381

Глава 12. Сериализация

413

Приложение. Соответствие статей второго издания
разделам третьего издания

445

Список литературы

449

Предметный указатель

453

Содержание
Вступительное слово

13

Предисловие
Предисловие к третьему изданию
Предисловие ко второму изданию
Предисловие к первому изданию

15
15
16
17

Благодарности
Благодарности к третьему изданию
Благодарности ко второму изданию
Благодарности к первому изданию
Ждем ваших отзывов!

19
19
20
21
22

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

25

Глава 2. Создание и уничтожение объектов
2.1. Рассмотрите применение статических фабричных методов
вместо конструкторов
2.2. При большом количестве параметров конструктора подумайте
о проектном шаблоне Строитель
2.3. Получайте синглтон с помощью закрытого конструктора
или типа перечисления
2.4. Обеспечивайте неинстанцируемость с помощью закрытого
конструктора
2.5. Предпочитайте внедрение зависимостей жестко
прошитым ресурсам
2.6. Избегайте создания излишних объектов
2.7. Избегайте устаревших ссылок на объекты
2.8. Избегайте финализаторов и очистителей
2.9. Предпочитайте try-с-ресурсами использованию try-f inally

29

Глава 3. Методы, общие для всех объектов
3.1. Перекрывая equals, соблюдайте общий контракт
3.2. Всегда при перекрытии equals перекрывайте hashCode

67
67
81

29

34
43
46
47
50
53
57
63

3.3. Всегда перекрывайте toString
3.4. Перекрывайте метод clone осторожно
3.5. Подумайте о реализации Comparable

87
90
100

Глава 4. Классы и интерфейсы
4.1. Минимизируйте доступность классов и членов
4.2. Используйте в открытых классах методы доступа,
а не открытые поля
4.3. Минимизируйте изменяемость
4.4. Предпочитайте композицию наследованию
4.5. Проектируйте и документируйте наследование
либо запрещайте его
4.6. Предпочитайте интерфейсы абстрактным классам
4.7. Проектируйте интерфейсы для потомков
4.8. Используйте интерфейсы только для определения типов
4.9. Предпочитайте иерархии классов дескрипторам классов
4.10. Предпочитайте статические классы-члены нестатическим
4.11. Ограничивайтесь одним классом верхнего уровня
на исходный файл

109
109

Глава 5. Обобщенное программирование
5.1. Не используйте несформированные типы
5.2. Устраняйте предупреждения о непроверяемом коде
5.3. Предпочитайте списки массивам
5.4. Предпочитайте обобщенные типы
5.5. Предпочитайте обобщенные методы
5.6. Используйте ограниченные символы подстановки
для повышения гибкости API
5.7. Аккуратно сочетайте обобщенные типы и переменное
количество аргументов
5.8. Применяйте безопасные с точки зрения типов
гетерогенные контейнеры

159
159
165
168
173
17 8

Глава 6. Перечисления и аннотации
6.1. Используйте перечисления вместо констант int
6.2. Используйте поля экземпляров вместо порядковых значений
6.3. Используйте EnumSet вместо битовых полей

203
203
216
217

114
117
125

131
138
144
147
149
152
156

183

190
196

6.4. Используйте ЕпшпМар вместо индексирования порядковыми
номерами
6.5. Имитируйте расширяемые перечисления с помощью интерфейсов
6.6. Предпочитайте аннотации схемам именования
6.7. Последовательно используйте аннотацию Override
6.8. Используйте интерфейсы-маркеры для определения типов

219
225
229
239
242

Глава 7. Лямбда-выражения и потоки
7.1. Предпочитайте лямбда-выражения анонимным классам
7.2. Предпочитайте ссылки на методы лямбда-выражениям
7.3. Предпочитайте использовать стандартные функциональные
интерфейсы
7.4. Разумно используйте потоки
7.5. Предпочитайте в потоках функции без побочных эффектов
7.6. Предпочитайте коллекции потокам в качестве
возвращаемых типов
7.7. Будьте внимательны при параллелизации потоков

245
245
250

Глава 8. Методы
8.1. Проверяйте корректность параметров
8.2. При необходимости создавайте защитные копии
8.3. Тщательно проектируйте сигнатуры методов
8.4. Перегружайте методы разумно
8.5. Используйте методы с переменным количеством аргументов
с осторожностью
8.6. Возвращайте пустые массивы и коллекции, а не null
8.7. Возвращайте Optional с осторожностью
8.8. Пишите документирующие комментарии для всех открытых
элементов API

283
283
287
292
294

Глава 9. Общие вопросы программирования
9.1. Минимизируйте область видимости локальных переменных
9.2. Предпочитайте циклы for для коллекции традиционным
циклам for
9.3. Изучите и используйте возможности библиотек
9.4. Если вам нужны точные ответы, избегайте float и double

321
321

252
257
265
271
277

302
304
307

312

324
328
331

9.5. Предпочитайте примитивные типы упакованным
примитивным типам
9.6. Избегайте применения строк там, где уместнее другой тип
9.7. Помните о проблемах производительности
при конкатенации строк
9.8. Для ссылки на объекты используйте их интерфейсы
9.9. Предпочитайте интерфейсы рефлексии
9.10. Пользуйтесь машинно-зависимыми методами осторожно
9.11. Оптимизируйте осторожно
9.12. Придерживайтесь общепринятых соглашений по именованию

Глава 10. Исключения
10.1. Используйте исключения только в исключительных ситуациях
10.2. Используйте для восстановления проверяемые исключения,
а для программных ошибок — исключения времени выполнения
10.3. Избегайте ненужных проверяемых исключений
10.4. Предпочитайте использовать стандартные исключения
10.5. Генерируйте исключения, соответствующие абстракции
10.6. Документируйте все исключения, которые может
генерировать метод
10.7. Включайте в сообщения информацию о сбое
10.8. Добивайтесь атомарности сбоев
10.9. Не игнорируйте исключения
Глава 11. Параллельные вычисления
11.1. Синхронизируйте доступ к совместно используемым
изменяемым данным
11.2. Избегайте излишней синхронизации
11.3. Предпочитайте исполнителей, задания и потоки данных
потокам исполнения
11.4. Предпочитайте утилиты параллельности методам wait
и notify
11.5. Документируйте безопасность с точки зрения потоков
11.6. Аккуратно применяйте отложенную инициализацию
11.7. Избегайте зависимости от планировщика потоков

334
338

341
342
344
348
350
353

359
359
362
365
367
370
372
374
376
378
381
381
386

394

396
402
406
409

Глава 12. Сериализация
12.1. Предпочитайте альтернативы сериализации Java
12.2. Реализуйте интерфейс Serializable крайне осторожно
12.3. Подумайте о применении пользовательской
сериализованной формы
12.4. Создавайте защищенные методы readOb j ect
12.5. Для управления экземпляром предпочитайте типы
перечислений методу readResolve
12.6. Подумайте о применении прокси-агента
сериализации вместо сериализованных экземпляров

413
413
418

421
429
435

440

Приложение. Соответствие статей второго издания
разделам третьего издания

445

Список литературы

449

Предметный указатель

453

Моей семье: Синди, Тиму и Мэтту

Ещё больше книг по Java в нашем телеграм канале:
https://t.me/javalib

Вступительное слово

Если ваш коллега скажет вам ‘‘Моя супруга сегодня перед ночью изготовит
дома необычную еду. Не объединишься с нами в поедании?”, то вам в голову,
вероятно, придут три мысли: вас пригласили на ужин; ваш коллега явно ино­
странец; ну, а первым вашим ощущением будет озадаченность.
Если вы когда-либо изучали иностранный язык, а затем пробовали поль­
зоваться им за пределами учебной аудитории, то вы понимаете, что есть три
вещи, которые необходимо знать: каким образом структурирован изучаемый
язык (его грамматику), какими словами обозначаются вещи, о которых вы хо­
тите рассказать (словарь), а также общепринятые и эффективные способы го­
ворить о повседневных вещах (лексические обороты). На занятиях слишком
часто уделяется внимание только первым двум темам, и позже вы обнаружи­
ваете, что настоящие носители изучаемого вами языка прячут улыбку, пытаясь
понять ваши обороты.
В случае языка программирования все обстоит почти так же. Вам необходи­
мо понимать основы языка: является он алгоритмическим, функциональным
или объектно-ориентированным. Вам нужно знать словарь языка: какие струк­
туры данных, операции и возможности предоставляют язык и его стандартные
библиотеки. Вам необходимо также ознакомиться с общепринятыми и эффек­
тивными способами структурирования вашего кода. В книгах, посвященных
языкам программирования, часто освещаются лишь первые два вопроса, а эф­
фективные приемы работы с языком если и обсуждаются, то лишь крайне
бегло. Возможно, дело в том, что писать на первые две темы гораздо проще.
Грамматика и словарь — это свойства самого языка, тогда как способ его ис­
пользования характеризует скорее людей, которые этим языком пользуются.
Например, язык программирования Java — объектно-ориентированный
язык с единичным наследованием, поддерживающий императивный (ориен­
тированный на инструкции) стиль программирования каждого метода. Его би­
блиотеки ориентированы на поддержку графического вывода, работы с сетью,
распределенных вычислений и безопасности. Однако как использовать этот
язык на практике наилучшим образом?

14

ВСТУПИТЕЛЬНОЕ СЛОВО

Имеется и другой аспект. Программы, в отличие от произнесенных предло­
жений, а также большинства книг и журналов, со временем меняются. Обычно
недостаточно создать код, который эффективно работает и легко может быть
понят другими людьми. Нужно еще и организовать этот код таким образом,
чтобы его можно было легко модифицировать. Практически для любой задачи
Т имеется с десяток вариантов написания решающего ее кода. Из этого десятка
семь окажутся запутанными, неэффективными или непонятными для читаю­
щего их человека. Но какой из оставшихся трех вариантов будет более всего
похож на исходный текст, который потребуется в следующем году при разра­
ботке новой версии программы, решающей задачу Т'1
Есть масса книг, по которым можно изучать грамматику языка программи­
рования Java, включая The Java™ Programming Language Арнольда (Arnold),
Гослинга (Gosling) и Холмса (Holmes) или The Java™ Language Specification
Гослинга, Джоя (Joy), Стила (Steele), Брача (Bracha) и Бакли (Buckley) [25].
Имеется также множество книг, посвященных библиотекам и API, связанным
с языком программирования Java.
Эта книга посвящена третьей теме: эффективному использованию языка
Java. Джошуа Блох провел несколько лет в Sun Microsystems, занимаясь рас­
ширением, реализацией и применением языка программирования Java; он
также прочел огромное количество исходных текстов, написанных многими
программистами, в том числе мной. Приведя свои знания и опыт в систему, он
дает отличные советы о том, каким образом структурировать код, чтобы он эф­
фективно работал, был понятен другим программистам, чтобы последующие
его изменения и усовершенствования доставляли как можно меньше хлопот и
даже по возможности чтобы ваши программы были к тому же элегантными и
красивыми.
Гай Л. Стил-младишй (Guy L. Steele Jr.)
Берлингтон, Массачусетс
Апрель 2001

Предисловие
ii

....... ■

...................... .. .

... .

;. ..■...

Предисловие к третьему изданию
В 1997 году, когда язык Java был еще новинкой, отец Java, Джеймс Гослинг
(James Gosling), описал его как “весьма простой язык для синих воротничков”
[14]. Примерно в то же время отец C++, Бьярне Страуструп (Bjame Stroustrup),
описал C++ как “мультипарадигменный язык”, который “сознательно сделан от­
личным от языков, созданных для поддержки единого способа написания про­
грамм” [47]. Касаясь языка программирования Java, Страуструп предупредил:

Большая часть относительной простоты языка программирования Ja­
va — как в большинстве новых языков — отчасти иллюзия, а отчасти
результат его незавершенности. Со временем размеры и сложность Java
значительно вырастут. Его размер удвоится или утроится, как увеличится
и количество зависящих от реализации расширений или библиотек [46].

Сейчас, двадцать лет спустя, можно сказать, что и Гослинг, и Страуструп
были правы. Java теперь большой и сложный язык программирования с мно­
жеством абстракций для множества вещей — от параллельного выполнения до
итераций и представления даты и времени.
Мне все еще нравится Java, хотя мой пыл несколько остыл с его ростом.
Учитывая увеличение размера и сложности языка, все более важной является
необходимость руководства по этому языку программирования, охватываю­
щего наилучшие современные практики его применения. В третьем издании
данной книги я сделал все, чтобы предоставить вам именно такое руковод­
ство. Я надеюсь, что это издание, оставаясь верным духу первых двух изданий,
удовлетворит всем вашим потребностям.
Сан-Хосе, Калифорния
Ноябрь 2017

P.S. Было бы упущением не упомянуть о передовой практике, которая ныне
отнимает изрядное количество моего времени. С момента рождения нашей об­
ласти в 1950-е годы мы, программисты, свободно обменивались и перепро­
ектировали API друг друга. Эта практика имела и имеет решающее значение

16

ПРЕДИСЛОВИЕ

для быстрых успехов компьютерных технологий. Я активно работаю для того,
чтобы сохранить эту свободу и далее [9], и призываю вас присоединиться ко
мне. Если мы сохраним право повторной реализации API друг друга, это будет
иметь решающее значение для дальнейшего здоровья всей нашей отрасли.

Предисловие ко второму изданию
С тех пор как я написал первое издание этой книги в 2001 году, в платформе
Java произошло много изменений, и я решил, что настала пора написать вто­
рое издание. Наиболее значимыми изменениями стали добавление обобщен­
ных типов, типов перечислений, комментариев, автоматической инкапсуляции,
циклов по диапазону в Java 5. Новшеством стало добавление новой библиоте­
ки java. util. concurrent, также появившейся в Java 5. Мне повезло, что
вместе с Гиладом Брачей я смог возглавить команду, разрабатывавшую новые
возможности языка. Мне также повезло работать в команде, разрабатывавшей
библиотеку параллельных вычислений и возглавляемой Дугом Ли (Doug Lea).
Другим значительным изменением платформы стало ее оснащение совре­
менными интегрированными средами разработки, такими как Eclipse, IntelliJ
IDEA и NetBeans, а также инструментами статического анализа, как, напри­
мер, FindBugs. Хотя я и не принимал в этом участия, но смог извлечь для себя
огромную выгоду и узнал, как все эти Инструменты влияют на опыт разработ­
ки на языке программирования Java.
В 2004 году я перешел из Sun в Google, однако продолжал принимать учас­
тие в разработке платформы Java в течение последних четырех лет, работая
в области API для параллельных вычислений и коллекций, используя офисы
Google и Java Community Process. Я также имел удовольствие использовать
платформу Java для разработки библиотек для внутреннего использования в
Google. Теперь я знаю, что чувствуют пользователи.
Когда в 2001 году я писал первое издание книги, моей основной целью
было поделиться моим опытом с читателями, чтобы они могли повторить мои
успехи и избежать моих неудач. Новый материал продолжает традицию ис­
пользования реальных примеров из библиотек платформы Java.
Успех первой редакции превзошел все мои ожидания, и, освещая новый ма­
териал, я сделал все возможное, чтобы сохранить дух предыдущего издания.
Избежать увеличения книги было невозможно — и теперь в ней вместо 57 уже
78 разделов. И я не просто добавил 23 новых раздела, а тщательно переработал
исходное издание, в том числе удалив некоторые ставшие неактуальными раз­
делы. В приложении вы можете увидеть, как соотносятся материалы данного
издания с материалом первого издания.

ПРЕДИСЛОВИЕ К ПЕРВОМУ ИЗДАНИЮ

17

В предисловии к первому изданию я писал, что язык программирования
Java и его библиотеки очень способствуют качеству и производительности
труда и что работать с ними — одно удовольствие. Изменения в версиях 5 и 6
сделали их еще лучше. Сейчас платформа Java гораздо больше и сложнее, чем
в 2001 году, но, познакомившись с ее проектными шаблонами и идиомами, ис­
пользующими новые возможности, вы улучшите свои программы и упростите
свою жизнь. Надеюсь, что это издание передаст вам мой энтузиазм и поможет
более эффективно и с большим удовольствием использовать платформу и ее
новые возможности.
Сан-Хосе, Калифорния
Апрель 2008

Предисловие к первому изданию
В 1996 году я направился на запад работать в компании JavaSoft (как она
тогда называлась), поскольку для меня было очевидно, что именно там проис­
ходят главные события. На протяжении пяти лет я работал архитектором би­
блиотек для платформы Java. Я проектировал, реализовывал и сопровождал
множество библиотек, а также давал консультации по многим другим библио­
текам. Руководить этими библиотеками в ходе становления платформы языка
Java — такая возможность выпадает только раз в жизни. Не будет преувеличе­
нием сказать, что я имел честь работать с некоторыми великими программис­
тами нашего поколения. В процессе работы я узнал о языке программирования
Java очень многое: что хорошо работает, а что — нет и как пользоваться язы­
ком и его библиотеками для получения наиболее эффективного результата.
Эта книга является попыткой поделиться с вами моим опытом, чтобы вы
могли повторить мои успехи и избежать моих ошибок. Формат книги я поза­
имствовал из книги Скотта Мейерса (Scott Meyers) Effective C++ [33], состоя­
щей из разделов, каждый из которых посвящен одному конкретному правилу,
позволяющему улучшить программы и проекты. Такой формат кажется мне
необычайно эффективным, и, надеюсь, вам он также понравится.
Во многих случаях я иллюстрирую разделы реальными примерами из би­
блиотек платформы Java. Говоря, что нечто можно сделать лучше, я стараюсь
привести исходный текст, который я писал сам, однако иногда я брал разра­
ботки моих коллег. Приношу мои искренние извинения, если, несмотря на все
старания, я кого-либо при этом обидел. Отрицательные примеры приведены
не для того, чтобы кого-то опорочить, а в соответствии с духом сотрудничес­
тва, чтобы все мы могли извлечь пользу из опыта тех, кто прошел этот путь
ранее.

18

ПРЕДИСЛОВИЕ

Хотя эта книга предназначена не только для людей, занимающихся разра­
боткой повторно используемых компонентов, она неизбежно отражает мой
опыт в написании таковых, накопленный за последние два десятилетия. Я при­
вык думать в терминах интерфейсов прикладного программирования (API) и
предлагаю вам поступать так же. Даже если вы не занимаетесь разработкой
повторно используемых компонентов, мышление в соответствующих терми­
нах может повысить качество разрабатываемых вами программ. Более того,
нередко случается писать многократно используемые компоненты, даже не по­
дозревая об этом: вы написали что-то полезное, поделились своим результа­
том с приятелем, и вскоре оказывается, что у вашего кода уже с полдюжины
пользователей. С этого момента вы лишаетесь возможности свободно менять
этот API и благодарите сами себя за усилия, которые потратили на его перво­
начальную разработку и которые позволили получить качественный продукт.
Мое особое внимание к разработке API может показаться несколько про­
тивоестественным для ярых приверженцев новых облегченных методик раз­
работки программного обеспечения, таких как “Экстремальное программи­
рование” [2]. В этих методиках особое значение придается написанию самой
простой программы, какая только сможет работать. Если вы пользуетесь одной
из таких методик, то вскоре обнаружите, что особое внимание к разработке
API вернется сторицей при последующем рефакторинге программы. Основ­
ной задачей рефакторинга является усовершенствование системной структуры,
а также исключение дублирования кода. Этой цели невозможно достичь, если
у системных компонентов нет хорошо продуманного и спроектированного API.
Ни один язык не идеален, но некоторые из них великолепны. Я обнаружил,
что язык программирования Java и его библиотеки в огромной степени спо­
собствуют повышению качества и производительности труда и что работать
с ними — одно удовольствие. Надеюсь, эта книга сможет передать вам мой
энтузиазм и сделает вашу работу с языком Java более эффективной и приятной.
Купертино, Калифорния
Апрель 2001

Благодарности

Благодарности к третьему изданию

Я благодарю читателей первых двух изданий за их восторженный прием
этой книги и принятие ее идей в свои сердца, а также за отзывы о том, какое
положительное влияние она оказала на них и их работу. Я благодарю тех пре­
подавателей, которые использовали эту книгу в своих учебных курсах, а также
те команды программистов, которые приняли ее как руководство в повседнев­
ной работе.
Я благодарю всю команду Addison-Wesley и Pearson за их отзывчивость,
профессионализм и терпение. В любых условиях мой редактор Грег Денч
(Greg Doench) оставался идеальным редактором и истинным джентльменом.
Боюсь, что у него прибавилось седин в результате работы над этой книгой,
и смиренно прошу у него за это прощения. Менеджер проекта Джули Наиль
(Julie Nahil) и редактор проекта Дана Вилсон (Dana Wilson) превзошли все мои
ожидания и надежды: всегда прилежны, оперативны, организованны и дружес­
ки настроены — как и редактор Ким Вимпсетт (Kim Wimpsett).
Я вновь получил лучшую команду рецензентов, какую только можно вооб­
разить, и я искренне благодарен каждому из них. Вот состав основной группы,
которая подробно проанализировала каждую главу: Синди Блох (Cindy Bloch),
Брайан Керниган (Brian Kernighan), Кевин Бурриллион (Kevin Bourrillion),
Джо Боубир (Joe Bowbeer), Уильям Чарджин (William Chargin), Джо Дарси
(Joe Darcy), Брайан Гётц (Brian Goetz), Тим Хеллоран (Tim Halloran), Стюарт
Маркс (Stuart Marks), Тим Пейерлс (Tim Peierls) и Йошики Шибата (Yoshiki
Shibata). Кроме того, свой вклад внесли такие рецензенты, как Маркус Биль
(Marcus Biel), Дэн Блох (Dan Bloch), Бет Боттос (Beth Bottos), Мартин Буххольц (Martin Buchholz), Майкл Дайамонд (Michael Diamond), Чарли Гэррод
(Charlie Garrod), Том Хоутин (Tom Hawtin), Дуг Ли (Doug Lea), Алексей Шипилёв (Aleksey Shipi^v), Лу Вассерман (Lou Wasserman) и Питер Вайнбергер
(Peter Weinberger). Их отзывы содержали многочисленные предложения по ис­
правлению материала книги, что привело к существенному улучшению книги
и спасло меня от множества затруднений.

20

БЛАГОДАРНОСТИ

Особую благодарность заслужили Уильям Чарджин, Дуг Ли и Тим Пейерлс,
которые помогали в “обкатке” многих идей в этой книге. Уильям, Дуг и Тим
неизменно щедро делились своими временем и знаниями.
Наконец, я благодарю мою жену, Синди Блох, которая не только с понима­
нием относилась к моей занятости, но и помогала во всем — от чтения черно­
виков до подготовки предметного указателя; словом, во всех тех неприятных
мелочах, которые неизбежно сопутствуют любому большому проекту.

Благодарности ко второму изданию
Я благодарю читателей первого издания за их восторженный прием этой
книги и принятие ее идей в свои сердца, а также за отзывы о том, какое по­
ложительное влияние она оказала на них и их работу. Я благодарю тех препо­
давателей, которые использовали эту книгу в своих учебных курсах, а также те
команды программистов, которые приняли ее как руководство в повседневной
работе.
Я благодарю всю команду Addison-Wesley за их отзывчивость, профессио­
нализм и терпение. В любых условиях мой редактор Грег Денч (Greg Doench)
оставался идеальным редактором и истинным джентльменом. Менеджер про­
екта Джули Наиль (Julie Nahil) всегда была такой, какой должен быть менед­
жер проекта: прилежной, оперативной, организованной и дружески настроен­
ной — как и редактор Барбара Вуд (Barbara Wood).
Я вновь получил лучшую команду рецензентов, какую только можно вооб­
разить, и я искренне благодарен каждому из них. Вот состав основной груп­
пы, которая подробно проанализировала каждую главу: Лекси Бугер (Lexi
Baugher), Синди Блох (Cindy Bloch), Бет Боттос (Beth Bottos), Джо Боубир (Joe
Bowbeer), Брайан Гётц (Brian Goetz), Тим Хеллоран (Tim Halloran), Брайан
Керниган (Brian Kernighan), Роб Кёнигсберг (Rob Konigsberg), Тим Пейерлс
(Tim Peierls), Билл Паф (Bill Pugh), Йошики Шибата (Yoshiki Shibata), Питер
Стаут (Peter Stout), Питер Вайнбергер (Peter Weinberger) и Франк Еллин (Frank
Yellin). Кроме того, свой вклад внесли такие рецензенты, как Пабло Беллвер
(Pablo Bellver), Дэн Блох (Dan Bloch), Дэн Борнстейн (Dan Bornstein), Кевин
Бурриллион (Kevin Bourrillion), Мартин Буххольц (Martin Buchholz), Джо
Дарси (Joe Darcy), Нил Гафтер (Neal Gafter), Лоренс Гонсальвес (Laurence
Gonsalves), Аарон Гринхаус (Aaron Greenhouse), Барри Хайес (Barry Hayes),
Питер Джонс (Peter Jones), Анжелика Ланжер (Angelika Langer), Дуг Ли
(Doug Lea), Боб Ли (Bob Lee), Джереми Мэнсон (Jeremy Manson), Том Мэй
(Tom Мау), Майк Мак-Клоски (Mike McCloskey), Андрей Терещенко (Andriy
Tereshchenko) и Пол Тима (Paul Тута). Их отзывы содержали многочисленные

БЛА ГОДА РНОСТИ К ПЕРВОМУ ИЗДА НИЮ

21

предложения по исправлению материала книги, что привело к существенному
улучшению книги и спасло меня от множества затруднений. Все оставшиеся в
книге огрехи — на моей совести.
Особую благодарность заслужили Дуг Ли и Тим Пейерлс, которые помога­
ли в “обкатке” многих идей в этой книге. Дуг и Тим неизменно щедро дели­
лись своими временем и знаниями.
Я благодарю моего менеджера в Google, Прабху Кришну (Prabha Krishna), за
ее неизменную поддержку и содействие.
Наконец, я благодарю мою жену, Синди Блох, которая не только с понима­
нием относилась к моей занятости, но и помогала во всем — от чтения черно­
виков до работы с Framemaker и подготовки предметного указателя.

Благодарности к первому изданию
Я благодарю Патрика Чана (Patrick Chan) за предложение написать эту кни­
гу и за то, что он увлек этой идеей управляющего редактора серии Лизу Френд­
ли (Lisa Friendly); Тима Линдхольма (Tim Lindholm), технического редактора
серии; а также Майка Хендриксона (Mike Hendrickson), исполнительного ре­
дактора Addison-Wesley. Я благодарю вас, Лиза, Тим и Майк, за то, что вы по­
могли мне довести этот проект до конца, за ваши сверхчеловеческое терпение
и непоколебимую веру в то, что однажды я напишу эту книгу.
Я благодарю Джеймса Гослинга (James Gosling) и его команду за создание
того, о чем я смог написать, а также многих разработчиков платформы Java,
шедших по стопам Джеймса. В частности, я благодарю моих коллег по Sun
Java Platform Tools and Libraries Group за идеи и поддержку. Эта команда вклю­
чает Эндрю Беннетта (Andrew Bennett), Джо Дарси (Joe Darcy), Нила Гафтера (Neal Gafter), Ирис Гарсию (Iris Garcia), Константина Кладько (Konstantin
Kladko), Яна Литтла (Ian Little), Майка Мак-Клоски (Mike McCloskey) и Марка
Рейнхольда (Mark Reinhold). Должен упомянуть также бывших членов коман­
ды — Дзенхуа Ли (Zhenghua Li), Билла Мэддокса (Bill Maddox) и Навиина
Сандживу (Naveen Sanjeeva).
Я благодарю моего менеджера Эндрю Беннета (Andrew Bennett) и директо­
ра Ларри Абрамса (Larry Abrahams) за их полную и активную поддержку этого
проекта. Я благодарю Рича Грина (Rich Green), вице-президента Java Software,
за создание условий, в которых инженеры могут мыслить творчески и публи­
ковать свои работы.
Судьба подарила мне лучшую команду рецензентов, о какой только можно
мечтать, и я искренне благодарен всем ее членам. Вот состав этой команды:
Эндрю Беннетт (Andrew Bennett), Синди Блох (Cindy Bloch), Дэн Блох (Dan

22

БЛАГОДАРНОСТИ

Bloch), Бет Боттос (Beth Bottos), Джо Боубир (Joe Bowbeer), Гилад Брача (Gilad
Bracha), Мэри Кампьон (Mary Campione), Джо Дарси (Joe Darcy), Дэвид Эк­
хардт (David Eckhardt), Джо Фиалли (Joe Fialli), Лиза Френдли (Lisa Friendly),
Джеймс Гослинг (James Gosling), Питер Хаггар (Peter Haggar), Дэвид Холмс
(David Holmes), Брайан Керниган (Brian Kernighan), Константин Кладько
(Konstantin Kladko), Дуг Ли (Doug Lea), Дзенхуа Ли (Zhenghua Li), Тим Линдхольм (Tim Lindholm), Майк Мак-Клоски (Mike McCloskey), Тим Пейерлс (Tim
Peierls), Марк Рейнхольд (Mark Reinhold), Кен Расселл (Ken Russell), Билл
Шеннон (Bill Shannon), Питер Стаут (Peter Stout), Фил Уадлер (Phil Wadler) и
два рецензента, оставшихся анонимными. Они сделали многочисленные пред­
ложения, которые привели к существенным улучшениям книги и спасли меня
от множества затруднений. Все оставшиеся в книге огрехи — на моей совести.
Многочисленные коллеги как из Sun, так и из других компаний, принимали
участие в технических обсуждениях, улучшивших качество книги. Среди про­
чих полезные идеи внесли Бен Гомес (Ben Gomes), Стеффен Граруп (Steffen
Grarup), Питер Кесслер (Peter Kessler), Ричард Рода (Richard Roda), Джон Роуз
(John Rose) и Дэвид Стутамир (David Stoutamire). Особую благодарность за­
служил Дуг Ли (Doug Lea), который помогал в “обкатке” многих идей в этой
книге. Дуг неизменно щедро делился своими временем и знаниями.
Я благодарю Джули Диниколу (Julie Dinicola), Джеки Дусетт (Jacqui
Doucette), Майка Хендриксона (Mike Hendrickson), Хизер Ольшик (Heather
Olszyk), Трейси Расс (Tracy Russ) и всю команду Addison-Wesley за их под­
держку и профессионализм. Даже при невероятно плотном графике они всегда
оставались дружелюбны и гостеприимны.
Я благодарю Гая Стила (Guy Steele) за вступительное слово к книге. Я поль­
щен, что он решил принять участие в этом проекте.
Наконец, я благодарю мою жену, Синди Блох, которая не только с понима­
нием относилась к моей занятости, но и помогала во всем — от чтения черно­
виков до работы с Framemaker и подготовки предметного указателя.

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

ЖДЕМ ВАШИХ ОТЗЫВОВ!

23

дайте нам знать, нравится ли вам эта книга, а также выскажите свое мнение о
том, как сделать наши книги более интересными для вас.
Отправляя письмо или сообщение, не забудьте указать название книги и ее
авторов, а также свой обратный адрес. Мы внимательно ознакомимся с вашим
мнением и обязательно учтем его при отборе и подготовке к изданию новых
книг.
Наши электронные адреса:
E-mail: info@dialektika.com
WWW: http://www.dialektika.com

Ещё больше книг по Java в нашем телеграм канале:
https://t.me/javalib

ГЛАВА

1

Введение
Эта книга разработана с тем, чтобы помочь вам максимально эффективно

использовать возможности языка программирования Java и его основных би­
блиотек java. lang, java.util и java. io, а также подпакетов наподобие
java .util. concurrent и java. util. function. Прочие библиотеки рас­
сматриваются эпизодически.
Эта книга состоит из 90 разделов, каждый из которых посвящен одному
правилу. В этих правилах собран опыт, который лучшие, наиболее опытные
программисты считают весьма полезным. Эти разделы сгруппированы в один­
надцать глав, каждая из которых охватывает один из аспектов проектирования
программного обеспечения. Книга не предназначена для чтения от корки до
корки: каждый раздел более или менее самодостаточен. Разделы снабжены пе­
рекрестными ссылками, позволяющими пройти собственный путь через книгу.
Со времени публикации предыдущего издания книги к платформе Java
было добавлено немало новых функциональных возможностей. Большинство
разделов этой книги тем или иным способом используют эти возможности.
В приведенной ниже таблице показано, где именно в первую очередь освеще­
ны те или иные ключевые особенности языка.
Функциональная возможность

Разделы

Лямбда-выражения
Потоки
Использование класса Optional
Методы по умолчанию в интерфейсах
try-с-ресурсами
@SafeVarargs
Модули

7.1-7.3
7.4-7.7
8.7
4.7
2.9
5.7
4.1

Версия Java

8
8
8
8
7
7
9

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

26

ГЛАВА I

ВВЕДЕНИЕ

иллюстрирующие многие проектные шаблоны и идиомы. Там, где это умест­
но, представлены перекрестные ссылки на стандартный справочник в этой
области [12].
Многие разделы содержат один или несколько примеров программ, иллюс­
трирующих некоторые практики, которых следует избегать. Такие примеры,
иногда известные как “антишаблоны” (антипаттерны), ясно указываются с по­
мощью комментариев наподобие
// Никогда этого не делайте!

В каждом случае поясняется, почему этот пример плох, и предлагается альтер­
нативный подход.
Эта книга не для начинающих: предполагается, что вы уже знакомы с Java.
Если это не так, обратитесь к одной из множества книг для новичков, таких как
Java Precisely Питера Сестофта (Peter Sestoft) [41 ]. Хотя данная книга должна
быть доступна любому обладающему рабочим знанием языка, она должна дать
пищу для размышлений даже самым “продвинутым” программистам.
Большинство правил этой книги вытекают из нескольких основополагаю­
щих принципов. Ясность и простота имеют первостепенное значение. Пользо­
ватель компонента никогда не должен удивляться его поведению. Компоненты
должны быть как можно меньшими, но не меньше, чем нужно. (В этой книге
термин компонент относится к любому повторно используемому элементу
программы, от отдельных методов до сложных каркасов, состоящих из не­
скольких пакетов.) Код должен повторно использоваться, а не копироваться.
Зависимости между компонентами должны быть сведены к минимуму. Ошиб­
ки должны обнаруживаться немедленно после того, как они сделаны, в идеа­
ле — во время компиляции.
Хотя правила из этой книги и не применяются постоянно во время работы,
в подавляющем большинстве случаев они характеризуют лучшие практики
программирования. Вы не должны следовать этим правилам рабски и без раз­
мышлений, но нарушать их следует лишь изредка и по уважительной причине.
Обучение искусству программирования, как и большинству других дисциплин,
состоит из, во-первых, обучения правилам и, во-вторых, обучения, когда эти
правила нарушать.
По большей части эта книга не посвящена вопросам производительности.
Речь идет о написании ясных, правильных, полезных, надежных, гибких и лег­
ких в обслуживании и поддержке программ. Если вы можете написать такую
программу, то добиться требуемой производительности — обычно относитель­
но простой вопрос (раздел 9.11). В некоторых разделах обсуждаются проблемы
производительности и в некоторых из них приводятся цифры, эту производи­
тельность характеризующие. Эти цифры, которые обычно дополнены словами

ГЛАВА I

ВВЕДЕНИЕ

27

“на моей машине”, следует рассматривать как в лучшем случае очень прибли­
зительные.
Для тех, кому это важно: у меня старенькая домашняя машина с четырехъ­
ядерным 3.5 ГГц процессором Intel Core i7-4770K с 16 Гбайтами DDR3-1866
CL9 RAM, работающая с Azul’s Zulu 9.0.0.15-версией OpenJDK, с операцион­
ной системой Microsoft Windows 7 Professional SP1 (64-bit).
При обсуждении возможностей языка программирования Java и его библио­
тек иногда необходимо сослаться на определенные версии. Для удобства в этой
книге используются краткие названия вместо официальных. В приведенной
далее таблице показано соответствие официальных названий используемым в
книге кратким названиям.
Официальное название выпуска

Краткое название

JDK 1.0.x
JDK 1.1.x
Java 2 Platform, Standard Edition,
Java 2 Platform, Standard Edition,
Java 2 Platform, Standard Edition,
Java 2 Platform, Standard Edition,
Java Platform, Standard Edition 6
Java Platform, Standard Edition 7
Java Platform, Standard Edition 8
Java Platform, Standard Edition 9

Java 1.0
Java 1.1
Java 2
Java 3
Java 4
Java 5
Java 6
Java 7
Java 8
Java 9

vl .2
vl.3
vl.4
v5.0

Примеры в книге достаточно полные, но удобочитаемость предпочтитель­
нее полноты. В них используются классы из пакетов java. util и java. io.
Для компиляции примеров, возможно, придется добавить одно или несколько
объявлений импорта или иной подобный шаблон. Веб-сайт книги по адресу
http: //joshbloch. com/effectivejava содержит расширенную версию
каждого примера, который можно скомпилировать и запустить.
По большей части в этой книге используются технические термины, опре­
деленные в книге The Java Language Specification, Java SE 8 Edition [25]. Не­
сколько терминов заслуживают отдельного упоминания. Язык поддержива­
ет четыре разновидности типов: интерфейсы (включая аннотации), классы
(включая перечисления), массивы и примитивы. Первые три называются ссы­
лочными типами. Экземпляры класса и массивы являются объектами', при­
митивные значения таковыми не являются. Члены класса включают его поля,
методы, классы-члены и интерфейсы-члены. Сигнатура метода состоит из его
имени и типов его формальных параметров; сигнатура не содержит тип возвра­
щаемого методом значения.

28

ГЛАВА I

ВВЕДЕНИЕ

В этой книге используется несколько терминов, отличных от используемых
в упомянутой книге. Здесь термин наследование (inheritance) используется как
синоним для создания подклассов (subclassing). Вместо использования терми­
на наследования для интерфейсов книга просто говорит, что класс реализует
интерфейс или что один интерфейс расширяет другой. Чтобы описать уровень
доступа, применяемый при отсутствии явного указания, в книге использует­
ся традиционный доступ закрытый на уровне пакета (package-private) вместо
технически правильного доступа пакета (package access) [25, 6.6.1].
Также здесь используется несколько технических терминов, которые не
определены в упомянутой книге. Термин экспортируемый API, или просто
API, ссылается на классы, интерфейсы, конструкторы, члены и сериализован­
ные формы, с помощью которых программист обращается к классу, интер­
фейсу или пакету. (Аббревиатуре “API”, означающей интерфейс прикладного
программирования, отдается предпочтение перед термином интерфейс, чтобы
избежать путаницы с конструкцией языка с этим названием.) Программист,
который пишет программу, которая использует API, именуется пользователем
API. Класс, реализация которого использует API, является клиентом API.
Классы, интерфейсы, конструкторы, члены и сериализованные формы вмес­
те называются элементами API. Экспортированный API состоит из элементов
API, которые доступны за пределами пакета, в котором определен API. Это
те элементы API, которые может использовать любой клиент и которые автор
API обязуется поддерживать. Не случайно они также являются элементами,
для которых утилита Javadoc генерирует документацию в режиме работы по
умолчанию. Грубо говоря, экспортируемый API пакета состоит из открытых
и защищенных членов и конструкторов каждого открытого класса или интер­
фейса в пакете.
В Java 9 в платформу была добавлена модульная система (module system).
Если библиотека использует модульную систему, ее экспортированный API
представляет собой объединение экспортированных API всех пакетов, экспор­
тируемых объявлением модуля библиотеки.

ГЛАВА

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

2.1. Рассмотрите применение статических
фабричных методов вместо конструкторов
Традиционный способ, которым класс позволяет клиенту получить экзем­
пляр, — предоставление открытого (public) конструктора. Существует еще
один метод, который должен быть частью инструментария каждого програм­
миста. Класс может предоставить открытый статический фабричный метод'.
Вот простой пример из Boolean (упакованный примитивный класс boolean).
Этот метод преобразует значение примитива типа boolean в ссылку на объект
Boolean:
public static Boolean valueOf(boolean b)
(
return b ? Boolean.TRUE : Boolean.FALSE;
)

Обратите внимание, что статический фабричный метод — это не то же самое,
что проектный шаблон Фабричный Метод (Factory Method) из [12]. Статичес­
кий фабричный метод, описанный в этом разделе, неимеет прямого эквива­
лента в [12].
Класс может предоставить своим клиентам статические фабричные методы
вместо открытых (public) конструкторов (или в дополнение к ним). Такое

1 Статический фабричный метод — это статический метод, который возвращает экзем­
пляр класса. — Примеч. ред.

30

ГЛАВА 2

СОЗДАНИЕ И УНИЧТОЖЕНИЕ ОБЪЕКТОВ

предоставление статического фабричного метода вместо открытого конструк­
тора имеет как преимущества, так и недостатки.
Одним из преимуществ статических фабричных методов является то,
что, в отличие от конструкторов, они имеют имена. Если параметры кон­
структора не описывают возвращаемые объекты (и сами по себе не являются
ими), то хорошо подобранное имя статического фабричного метода легче в ис­
пользовании, а получающийся в результате код оказывается более удобочитае­
мым. Например, вместо конструктора Biginteger (int, int, Random), кото­
рый возвращает объект Biginteger, который, вероятно, представляет собой
простое число, было бы лучше использовать статический фабричный метод с
именем Biginteger .probablePrime (этот метод был добавлен в Java 4).
Класс может иметь только один конструктор с заданной сигнатурой. Про­
граммисты, как известно, обходят это ограничение путем предоставления кон­
структоров, списки параметров которых отличаются только порядком типов их
параметров. Это плохая идея. Пользователь такого API не сможет вспомнить,
какой конструктор ему нужен, и в конечном итоге будет ошибаться и вызывать
неверный конструктор. Программисты, читающие код с такими конструктора­
ми, без документации не будут знать, что делает этот код.
Благодаря наличию имен на статические фабричные методы не наклады­
вается ограничение из предыдущего абзаца. В тех случаях, когда классу, как
представляется, требуется несколько конструкторов с одинаковой сигнатурой,
замените конструкторы статическими фабричными методами с тщательно по­
добранными именами, чтобы подчеркнуть их различия.
Вторым преимуществом статических фабричных методов является то,
что, в отличие от конструкторов, они не обязаны создавать новые объ­
екты при каждом вызове. Это позволяет неизменяемым классам (раздел 4.3)
использовать предварительно сконструированные экземпляры или кешировать
экземпляры при их создании, чтобы избежать создания ненужных дубликатов
объектов. Метод Boolean. valueOf (boolean) иллюстрирует этот метод: он
никогда не создает объект. Этот метод аналогичен проектному шаблону При­
способленец (Flyweight) [12]. Он может значительно улучшить производитель­
ность, если часто запрашиваются эквивалентные объекты, особенно если их
создание является дорогостоящим.
Возможность статических фабричных методов возвращать один и тот же
объект при повторных вызовах позволяет классам строго контролировать, ка­
кие экземпляры существуют в любой момент времени. Классы, которые ра­
ботают таким образом, называются классами с управлением экземплярами
(instance-controlled). Существует несколько причин для написания таких клас­
сов. Управление экземплярами позволяет классу гарантировать, что он явля­
ется синглтоном (раздел 2.3) или неинстанцируемым (раздел 2.4). Кроме того,
это позволяет неизменяемому классу значения (раздел 4.3) гарантировать, что

2.1. РАССМОТРИТЕ ПРИМЕНЕНИЕ СТАТИЧЕСКИХ ФАБРИЧНЫХ МЕТОДОВ...

31

не существует двух одинаковых экземпляров: а . equals (Ь) истинно тогда
и только тогда, когда а==Ь. Это основа проектного шаблона Приспособле­
нец [12]. Такую гарантию предоставляют типы перечислений (раздел 6.1).
Третье преимущество статических фабричных методов заключается в
том, что, в отличие от конструкторов, они могут возвращать объект лю­
бого подтипа их возвращаемого типа. Это дает вам большую гибкость в вы­
боре класса возвращаемого объекта.
Одним из применений этой гибкости является то, что API может возвращать
объекты, не делая их классы открытыми. Сокрытие классов реализации таким
способом приводит к очень компактному API. Эта техника ведет к каркасам на
основе интерфейсов (раздел 4.6), в которых интерфейсы предоставляют естес­
твенные возвращаемые типы для статических фабричных методов.
До Java 8 интерфейсы не могли иметь статических методов. По соглаше­
нию статические фабричные методы для интерфейса с именем Туре размеща­
лись в сопутствующем неинстанцируемом классе (раздел 2.4) с именем Types.
Например, Java Collections Framework содержит 45 реализаций интерфейсов,
предоставляя немодифицируемые коллекции, синхронизированные коллекции
и т.п. Почти все эти реализации экспортируются с помощью статических фа­
бричных методов в одном неинстанцируемом классе (java .util. Collec­
tions). Все классы возвращаемых объектов являются закрытыми.
Collections Framework API гораздо меньше, чем потребовалось бы в случае
экспорта 45 отдельных открытых классов, по одному для каждой реализации.
Это не только уменьшенный размер API, но и меньший концептуальный вес:
количество и сложность концепций, которые программисты должны освоить
для того, чтобы использовать API. Программист знает, что возвращаемый объ­
ект имеет API, в точности предусмотренный его интерфейсом, так что нет не­
обходимости читать дополнительную документацию для класса реализации.
Кроме того, использование такого статического фабричного метода требует
от клиента обращения к возвращаемому объекту через интерфейс, а не через
класс реализации, что в общем случае является хорошей практикой (раздел 9.8).
В Java 8 было ликвидировано ограничение, что интерфейсы не могут содер­
жать статические методы, так что теперь мало причин для предоставления неинстанцируемого сопутствующего класса для интерфейса. Многие открытые
статические члены, которые ранее располагались в таком классе, теперь раз­
мещаются в самом интерфейсе. Обратите, однако, внимание, что по-прежнему
может оставаться необходимым поместить основную часть кода реализации
этих статических методов в отдельный класс, закрытый на уровне пакета. Дело
в том, что Java 8 требует, чтобы все статические члены интерфейса были от­
крытыми. Java 9 разрешает иметь закрытые статические методы, но статичес­
кие поля и статические классы-члены по-прежнему обязаны быть открытыми.

32

ГЛАВА 2

СОЗДАНИЕ И УНИЧТОЖЕНИЕ ОБЪЕКТОВ

Четвертым преимуществом статических фабричных методов является
то, что класс возвращенного объекта может варьироваться от вызова к
вызову в зависимости от входных параметров. Допускается любой подтип
объявленного типа возвращаемого значения. Класс возвращенного объекта мо­
жет также изменяться от выпуска к выпуску.
Класс EnumSet (раздел 6.3) не имеет открытых конструкторов, а только ста­
тические фабрики. В реализации OpenJDK они возвращают экземпляр одно­
го из двух подклассов в зависимости от размера базового типа перечисления:
если в нем не более 64 элементов (как в большинстве перечислений), то ста­
тические фабрики возвращают экземпляр RegularEnumSet, который реали­
зуется как один long; если же перечисление содержит больше 64 элементов,
фабрики возвращают экземпляр JumboEnumSet с массивом long.
Существование этих двух реализаций классов является невидимым для
клиентов. Если RegularEnumSet перестанет давать преимущества в произ­
водительности для малых перечислений, он может быть устранен из будущих
версий без каких бы то ни было последствий. Аналогично в будущую версию
можно добавить третью или четвертую реализацию EnumSet, если она ока­
жется полезной для производительности. Клиенты не знают и не должны бес­
покоиться о классе объекта, который они получают от фабрики; для них важно
только, что это некоторый подкласс EnumSet.
Пятое преимущество статических фабрик заключается в том, что класс
возвращаемого объекта не обязан существовать во время разработки
класса, содержащего метод. Такие гибкие статические фабричные методы
образуют основу каркасов провайдеров служб (service provider frameworks)
наподобие Java Database Connectivity API (JDBC). Каркас провайдера службы
представляет собой систему, в которой провайдер реализует службу, а система
делает реализацию доступной для клиентов, отделяя клиентов от реализаций.
Имеется три основных компонента каркаса провайдера службы: интерфейс
службы, который представляет реализацию; API регистрации провайдера, ко­
торый провайдеры используют для регистрации реализации; и API доступа
к службе, который клиенты используют для получения экземпляров службы.
API доступа к службе может позволить клиентам указать критерии выбора ре­
ализации. В отсутствие таких критериев API возвращает экземпляр реализа­
ции по умолчанию или позволяет клиенту циклически обойти все имеющиеся
реализации. API доступа к службе представляет собой гибкую статическую
фабрику, которая лежит в основе каркаса провайдера службы.
Необязательный четвертый компонент каркаса провайдера службы пред­
ставляет собой интерфейс провайдера службы, который описывает объект
фабрики, производящий экземпляры интерфейса службы. В отсутствие интер­
фейса провайдера службы реализации должны инстанцироваться рефлективно

2.1. РАССМОТРИТЕ ПРИМЕНЕНИЕ СТАТИЧЕСКИХ ФАБРИЧНЫХ МЕТОДОВ...

33

(раздел 9.9). В случае JDBC Connection играет роль части интерфейса служ­
бы, DriverManager. registerDriver представляет собой API регистрации
провайдера, DriverManager. getConnection — API доступа к службе, а
Driver — интерфейс провайдера службы.
Имеется множество вариантов шаблонов каркасов провайдеров служб.
Например, API доступа к службе может возвращать более богатый интер­
фейс службы клиентам, чем представленной провайдерами. Это проектный
шаблон Мост (Bridge) [12]. Каркасы (фреймворки) внедрения зависимостей
(раздел 2.5) можно рассматривать как мощные провайдеры служб. Начиная
с Java 6 платформа включает каркас провайдера служб общего назначения,
java. util. ServiceLoader, так что вам не нужно (а в общем случае и не
стоит) писать собственный каркас (раздел 9.3). JDBC не использует ServiceLoader, так как предшествует ему.
Основное ограничение предоставления только статических фабрич­
ных методов заключается в том, что классы без открытых или защищен­
ных конструкторов не могут порождать подклассы. Например, невозмож­
но создать подкласс любого из классов реализации в Collections Framework.
Пожалуй, это может быть благом, потому что требует от программистов ис­
пользовать композицию вместо наследования (раздел 4.4), и необходимо для
неизменяемых типов (раздел 4.3).
Вторым недостатком статических фабричных методов является то, что
их трудно отличить от других статических методов. Они не выделены в до­
кументации API так же, как конструкторы, поэтому может быть трудно понять,
как создать экземпляр класса, предоставляемый статическим фабричным мето­
дом вместо конструкторов. Инструментарий Javadoc, возможно, когда-нибудь
обратит внимание на статические фабричные методы. Указанный недостаток
может быть смягчен путем привлечения внимания к статическим фабричным
методам в документации класса или интерфейса, а также путем применения
соглашений по именованию. Ниже приведены некоторые распространенные
имена для статических фабричных методов, и этот список является далеко не
исчерпывающим.

• from — метод преобразования типа, который получает один параметр и
возвращает соответствующий экземпляр требуемого типа, например:
Date d = Date.from(instant);

• of — метод агрегации, который получает несколько параметров и воз­
вращает соответствующий экземпляр требуемого типа, объединяющий
их, например:
Set faceCards = EnumSet.of(JACK, QUEEN, KING);

34

ГЛАВА 2

СОЗДАНИЕ И УНИЧТОЖЕНИЕ ОБЪЕКТОВ

• valueOf — более многословная альтернатива from и of, например:
Biginteger prime = Biginteger.valueOf(Integer.MAX_VALUE) ;

• instance или getlnstance — возвращает экземпляр, описываемый
параметрами (если таковые имеются), но о котором нельзя сказать, что он
имеет то же значение, например:
StackWalker luke = StackWalker.getlnstance(options);

• create или newlnstance — подобен instance или getlnstance, но
отличается тем, что гарантирует, что каждый вызов дает новый экзем­
пляр, например:
Object newArray = Array.newlnstance(classObject, arrayLen);

• getType — подобен getlnstance, но используется, если фабричный
метод находится в другом классе. Туре представляет собой тип объекта,
возвращаемого фабричным методом, например:
Filestore fs = Files.getFileStore(path);

• new Type — подобен newlnstance, но используется, если фабричный
метод находится в другом классе. Туре представляет собой тип объекта,
возвращаемого фабричным методом, например:
BufferedReader br = Files.newBufferedReader(path);

• type — краткая альтернатива для get Type и new Type, например:
List litany = Collections.list(legacyLitany);

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

2.2. При большом количестве параметров конструктора
подумайте о проектном шаблоне Строитель
Статические фабрики и конструкторы имеют общее ограничение: они не
масштабируются для большого количества необязательных параметров. Рас­
смотрим случай класса, представляющего этикетку Nutrition Facts, которая
имеется на упакованных пищевых продуктах. Эти этикетки имеют несколько
обязательных полей — размер порции, число порций в упаковке, калорийность
порции, а также более двадцати необязательных полей — количество жира,

2.2. ПРИ БОЛЬШОМ КОЛИЧЕСТВЕ ПАРАМЕТРОВ КОНСТРУКТОРА ПОДУМАЙТЕ...

35

содержание насыщенных жиров, трансжиров, холестерина, натрия и т.д. Боль­
шинство продуктов имеют ненулевые значения только для нескольких из этих
необязательных полей.
Какие конструкторы или статические фабрики следует написать для тако­
го класса? Традиционно программисты используют шаблон телескопического
конструктора, когда предоставляется конструктор только с необходимыми па­
раметрами, другой — с одним необязательным параметром, третий — с двумя
необязательными параметрами и так далее до конструктора со всеми необяза­
тельными параметрами. Вот как это выглядит на практике (для краткости по­
казаны только четыре необязательных поля).
// Шаблон телескопического конструктора - не масштабируется!
public class NutritionFacts
{
private final int servingSize;
//(мл в порции)
Необходим
private final int servings;
//(количество порций)Необходим
private final int calories;
//(калорий в порции) Необязателен
private final int fat;
//(жиров в порции)
Необязателен
private final int sodium;
//(Na в порции)
Необязателен
private final int carbohydrate; //(углеводы в порции)Необязателен
public NutritionFacts(int servingSize, int servings)
{
this(servingSize, servings, 0);
}
public NutritionFacts(int servingSize, int servings,
int calories)
{
this(servingSize, servings, calories, 0) ;
}
public NutritionFacts(int servingSize, int servings,
int calories, int fat)
this(servingSize, servings, calories, fat, 0) ;
}
public NutritionFacts(int servingSize, int servings,
int calories, int fat, int sodium)
(
this(servingSize, servings, calories, fat, sodium, 0) ;
}
public NutritionFacts(int servingSize, int servings,
int calories, int fat, int sodium,
int carbohydrate)
{
this.servingSize = servingSize;
this.servings = servings;
this.calories = calories;
this.fat = fat;

36

ГЛАВА 2

СОЗДАНИЕ И УНИЧТОЖЕНИЕ ОБЪЕКТОВ

this.sodium = sodium;
this.carbohydrate = carbohydrate;

}
}

Когда вы хотите создать экземпляр этого класса, вы используете конструк­
тор с наиболее коротким списком параметров, который содержит все парамет­
ры, которые вы хотите установить:
NutritionFacts cocaCola = new NutritionFacts(240, 8, 100, 0, 35, 27);

Обычно такой вызов конструктора требует множества параметров, которые
вы не хотите устанавливать, но вынуждены передавать для них значения так
или иначе. В данном случае мы передали значение 0 для жиров. Когда есть
“всего лишь” шесть параметров, это может показаться не таким уж страшным,
но по мере увеличения количества параметров ситуация быстро выходит изпод контроля.
Короче говоря, шаблон телескопического конструктора работает, но
очень трудно написать код клиента, у которого есть много параметров,
и еще труднее его читать. Читатель исходного текста должен гадать, что оз­
начают все эти значения, и тщательно рассчитывать позицию интересующего
параметра. Длинные последовательности одинаково типизированных парамет­
ров могут вести к трудно обнаруживаемым ошибкам. Если клиент случайно
поменяет местами два таких параметра, компилятор не будет жаловаться, но во
время выполнения программа будет вести себя неверно (раздел 8.3).
Еще одной альтернативой при наличии большого количества необязатель­
ных параметров в конструкторе является шаблон JavaBeans, в котором для соз­
дания объекта вызывается конструктор без параметров, а затем вызываются
методы для задания каждого обязательного параметра и всех необязательных
параметров, требуемых в конкретной ситуации.
// Шаблон JavaBeans - обеспечивает изменяемость
public class NutritionFacts
(
// Параметры инициализируются значениями
// по умолчанию (если таковые имеются)
// Необходим; значения по умолчанию нет:
private int servingSize - -1;
// Необходим; значения по умолчанию нет:
private int servings = -1;

private int calories = 0;
private int fat = 0;
private int sodium = 0;
private int carbohydrate = 0;
public NutritionFacts () { }

2.2. ПРИ БОЛЬШОМ КОЛИЧЕСТВЕ ПАРАМЕТРОВ КОНСТРУКТОРА ПОДУМАЙТЕ...

У]

// Методы установки значений
public void setServingSize(int val)
{
servingSize = val;
}
public void setServings(int val)
{
servings = val;
}
public void setCalories(int val)
(
calories = val;
}
public void setFat(int val)
(
fat = val;
}
public void setSodium(int val)
{
sodium = val;
}
public void setCarbohydrate(int val)
{
carbohydrate = val;
}

}

У этого шаблона нет ни одного из недостатков шаблона телескопического
конструктора. Создание экземпляров оказывается немного многословным, но
легким и для написания, и для чтения:
NutritionFacts cocaCola = new NutritionFacts();
cocaCola.setServingSize(240) ;
cocaCola.setServings(8);
cocaCola.setCalories(100) ;
cocaCola.setSodium(35);
cocaCola.setCarbohydrate(27) ;

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

38

ГЛАВА 2

СОЗДАНИЕ И УНИЧТОЖЕНИЕ ОБЪЕКТОВ

(раздел 4.3) и требует дополнительных усилий со стороны программиста для
обеспечения безопасности с точки зрения потоков.
Эти недостатки можно уменьшить, вручную “замораживая” объект после
завершения его строительства и не позволяя использовать его до тех пор, пока
он не будет разморожен, — но этот вариант громоздкий и редко используется
на практике. Кроме того, он может привести к ошибкам времени выполнения,
поскольку компилятор не может гарантировать, что программист вызвал метод
заморозки объекта перед его использованием.
К счастью, существует третий вариант, который сочетает в себе безопас­
ность шаблона телескопического конструктора и удобочитаемость шаблона
JavaBeans. Это разновидность проектного шаблона Строитель (Builder) [12].
Вместо того чтобы создавать объект непосредственно, клиент вызывает кон­
структор (или статическую фабрику) со всеми необходимыми параметрами и
получает объект строителя. Затем клиент вызывает методы установки полей
объекта строителя для задания каждого необязательного параметра, представ­
ляющего интерес. Наконец, клиент вызывает метод build параметров для соз­
дания объекта (обычно неизменяемого). Строитель обычно представляет собой
статический класс- член (раздел 4.10) класса, который он строит. Вот как это
выглядит на практике.
// Шаблон Builder
public class NutritionFacts
{
private final int servingSize;
private final int servings;
private final int calories;
private final int fat;
private final int sodium;
private final int carbohydrate;
public static class Builder
{
// Необходимые параметры
private final int servingSize;
private final int servings;
// Необязательные параметры — инициализированы
// значениями по умолчанию
private int calories = 0;
private int fat = 0;
private int sodium = 0;
private int carbohydrate = 0;

public Builder(int servingSize, int servings)
{
this.servingSize = servingSize;
this.servings = servings;

2.2.

ПРИ БОЛЬШОМ КОЛИЧЕСТВЕ ПАРАМЕТРОВ КОНСТРУКТОРА ПОДУМАЙТЕ...

39

public Builder calories (int val)
{
calories = val;
return this;
}
public Builder fat(int val)
(
fat = val;
return this;
}
public Builder sodium(int val)
{
sodium = val;
return this;
}
public Builder carbohydrate(int val)
{
carbohydrate = val;
return this;
}
public NutritionFacts build()
{
return new NutritionFacts(this);
}

}
private NutritionFacts(Builder builder)
{
servingSize = builder.servingSize;
servings
= builder.servings;
calories
= builder.calories;
fat
= builder.fat;
sodium
= builder.sodium;
carbohydrate = builder.carbohydrate;

Класс NutritionFacts неизменяемый, и все значения параметров по
умолчанию находятся в одном месте. Методы установки полей строителя воз­
вращают сам строитель, так что эти вызовы можно объединять в цепочки, по­
лучая потоковый (fluent) API. Вот как выглядит код клиента:
NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8)
.calories(100).sodium(35).carbohydrate(27).build();

Такой клиентский код легко писать, а главное — легко читать. Шаблон Стро­
итель имитирует именованные необязательные параметры, как в языках
программирования Python или Scala.
Проверка корректности параметров для краткости опущена. Чтобы как мож­
но скорее обнаруживать недопустимые параметры, правильность параметров

40

ГЛАВА 2

СОЗДАНИЕ И УНИЧТОЖЕНИЕ ОБЪЕКТОВ

проверяется в конструкторе и методах строителя. Проверка инвариантов
включает несколько параметров в конструкторе, вызываемом методом build.
Чтобы защитить эти инварианты, проверка полей объекта выполняется после
копирования параметров из строителя (раздел 8.2). Если проверка не пройде­
на, генерируется исключение IllegalArgumentException (раздел 10.4), в
котором подробно указывается, какие параметры оказались недопустимыми
(раздел 10.7).
Шаблон Строитель хорошо подходит для иерархий классов. Исполь­
зуйте параллельные иерархии строителей, в которых каждый вложен в со­
ответствующий класс. Абстрактные классы имеют абстрактных строителей;
конкретные классы имеют конкретных строителей. Например, рассмотрим аб­
страктный класс в корне иерархии, представляющей различные виды пиццы:
// Шаблон Строитель для иерархий классов
public abstract class Pizza
{
public enum Topping { HAM, MUSHROOM, ONION, PEPPER, SAUSAGE }
final Set toppings;
abstract static class Builder
(
EnumSet toppings = EnumSet.noneOf(Topping.class) ;
public T addTopping(Topping topping)
(
toppings.add(Objects.requireNonNull(topping)) ;
return self();
}
abstract Pizza build();

// Подклассы должны перекрывать этот метод, возвращая "себя"
protected abstract Т self();
}
Pizza(Builder builder)
I
toppings = builder.toppings.clone(); // См. раздел 8.2
}

}

Обратите внимание, что Pizza.Builder является обобщенным типом с
рекурсивным параметром типа (раздел 5.5). Это, наряду с абстрактным ме­
тодом self, обеспечивает корректную работу цепочек методов в подклассах
без необходимости приведения типов. Этот обходной путь для того факта, что
в Java нет “типа самого себя” (или “собственного типа”), известен как идиома
имитации собственного типа.
Вот два конкретных подкласса класса Pizza, один из которых представ­
ляет стандартную Нью-Йоркскую пиццу, другой — Кальцоне. Первый имеет

2.2.

ПРИ БОЛЬШОМ КОЛИЧЕСТВЕ ПАРАМЕТРОВ КОНСТРУКТОРА ПОДУМАЙТЕ...

41

необходимый параметр размера, а второй позволяет указать, где должен нахо­
диться соус — внутри или снаружи:
public class NyPizza extends Pizza
{
public enum Size { SMALL, MEDIUM, LARGE }
private final Size size;
public static class Builder extends Pizza.Builder
{
private final Size size;
public Builder(Size size)
(
this.size = Objects.requireNonNull(size);
}
^Override public NyPizza build()
<
return new NyPizza(this);
}
^Override protected Builder self()
{
return this;
}
}
private NyPizza(Builder builder)
(
super(builder);
size = builder.size;
}
}
public class Calzone extends Pizza
{
private final boolean sauceinside;
public static class Builder extends Pizza.Builder
{
private boolean sauceinside = false; // По умолчанию
public Builder sauceinside()
{
sauceinside = true;
return this;
}
^Override public Calzone build()
{
return new Calzone(this);
}
(^Override protected Builder self()
{
return this;

42

ГЛАВА 2

СОЗДАНИЕ И УНИЧТОЖЕНИЕ ОБЪЕКТОВ

private Calzone(Builder builder)
(
super(builder);
sauceinside = builder.sauceinside;
)
}

Обратите внимание, что метод build в строителе каждого подкласса объ­
является как возвращающий корректный подкласс: метод build класса NyP­
izza. Builder возвращает NyPizza, в то время как в Calzone . Builder
возвращается Calzone. Эта методика, в которой метод подкласса объявляется
как возвращающий подтип возвращаемого типа, объявленного в суперклассе,
известна как ковариантное типизирование возврата. Она позволяет клиентам
использовать эти строители без необходимости приведения типов.
Клиентский код этих “иерархических строителей”, по сути, идентичен коду
простого строителя NutritionFacts. В примере кода клиента, показанном
далее, для краткости предполагается статический импорт констант перечис­
ления:
NyPizza pizza = new NyPizza.Builder(SMALL)
.addTopping(SAUSAGE).addTopping(ONION).build();
Calzone calzone = new Calzone.Builder()
.addTopping(HAM).sauceinside().build();

Незначительное преимущество строителей над конструкторами заключает­
ся в том, что строители могут иметь несколько переменных (необязательных)
параметров, поскольку каждый параметр указан в собственном методе. Кроме
того, строители могут собирать параметры, передаваемые в нескольких вы­
зовах метода, в едином поле, как было показано в методе addTopping выше.
Проектный шаблон Строитель весьма гибкий. Один строитель может ис­
пользоваться многократно для создания нескольких объектов. Параметры стро­
ителя могут корректироваться между вызовами метода build для небольших
изменений создаваемых объектов. Строитель может автоматически заполнять
некоторые поля при создании объекта, например серийный номер, который
увеличивается каждый раз при создании нового объекта.
Конечно же, проектный шаблон Строитель имеет и недостатки. Чтобы соз­
дать объект, необходимо сначала создать его строитель. На практике обычно
стоимость создания строителя будет незаметной, но может оказаться пробле­
мой в смысле производительности в критических ситуациях. Кроме того, ша­
блон Строитель является более многословным, чем шаблон телескопического
конструктора, поэтому его следует использовать только при наличии достаточ­
но большого количества параметров, по крайней мере не менее четырех. Но
не забывайте, что в будущем могут добавиться и другие параметры. Но если

2.3. ПОЛУЧАЙТЕ СИНГЛТОН С ПОМОЩЬЮ ЗАКРЫТОГО КОНСТРУКТОРА...

43

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

2.3. Получайте синглтон с помощью закрытого
конструктора или типа перечисления
Синглтон — это просто класс, который инстанцируется только один раз
[12]. Синглтоны обычно представляют собой либо объект без состояния, такой
как функция (раздел 4.10), либо системный компонент, который уникален по
своей природе. Превращение класса в синглтон может затруднить тести­
рование его клиентов, потому что невозможно заменить ложную реализацию
синглтоном, если только он не реализует интерфейс, который служит в качес­
тве его типа.
Имеется два распространенных способа реализации классов синглтонов.
Оба они основаны на создании закрытого конструктора и экспорте открытого
статического элемента для предоставления доступа к единственному экземпля­
ру. В первом подходе член представляет собой поле final:
// Синглтон с полем public final
public class Elvis
(

public static final Elvis INSTANCE = new Elvis();
private Elvis()
{

}
public void leaveTheBuilding ()
{

44

ГЛАВА 2

СОЗДАНИЕ И УНИЧТОЖЕНИЕ ОБЪЕКТОВ

Закрытый конструктор вызывается только один раз для инициализации от­
крытого статического final-поля Elvis . INSTANCE. Отсутствие открытого
или защищенного конструктора гарантирует “одноэлвисность”: после иници­
ализации класса Elvis будет существовать ровно один экземпляр класса El­
vis — ни больше, ни меньше. Что бы ни делал клиент, это ничего не изменит
(с одной оговоркой: привилегированный клиент может вызвать закрытый кон­
структор рефлексивно (раздел 9.9) с помощью метода AccessibleObject.
setAccessible). Если вам нужно защититься от такого нападения, измените
конструктор так, чтобы он генерировал исключение при попытках создания
второго экземпляра.
Во втором подходе к реализации классов-синглтонов открытый член пред­
ставляет собой статический фабричный метод:
// Синглтон со статической фабрикой
public class Elvis
{
private static final Elvis INSTANCE = new Elvis();
private Elvis()
{
}

public static Elvis getlnstance()
{

return INSTANCE;
}
public void leaveTheBuilding()
{
}
}

Все вызовы Elvis . getlnstance возвращают ссылку на один и тот же объ­
ект, и никакой другой экземпляр Elvis никогда не будет создан (с той же ранее
упомянутой оговоркой).
Главным преимуществом подхода с открытым полем является то, что API
делает очевидным, что класс является синглтоном: открытое статическое поле
объявлено как final, так что оно всегда будет содержать ссылку на один и тот
же объект. Вторым преимуществом является то, что он проще.
Одним из преимуществ подхода со статической фабрикой является то, что
он обеспечивает достаточную гибкость для того, чтобы изменить синглтон на
класс, не являющийся таковым, без изменения его API. Фабричный метод воз­
вращает единственный экземпляр, но может быть изменен таким образом, что­
бы возвращать, скажем, отдельный экземпляр для каждого вызывающего его
потока. Вторым преимуществом является то, что можно написать обобщенную

2.3. ПОЛУЧАЙТЕ СИНГЛТОН С ПОМОЩЬЮ ЗАКРЫТОГО КОНСТРУКТОРА ..

45

фабрику синглтонов, если таковая требуется вашему приложению (раздел 5.5).
Последнее преимущество использования статической фабрики состоит в том,
что ссылка на метод может использоваться в качестве поставщика, например
Elvis: : instance является Supplier. Если ни одно из этих пре­
имуществ не является значимым, предпочтительнее оказывается подход с от­
крытым полем.
Чтобы сделать класс синглтона, который использует один из этих подходов,
сериализуемым (глава 12, “Сериализация”), недостаточно просто добавить в
его объявление implements Serializable. Для гарантии сохранения свой­
ства синглтона объявите все поля экземпляра как transient и предоставьте
метод readResolve (раздел 12.5). В противном случае при каждой десериали­
зации сериализованного экземпляра будет создаваться новый экземпляр, что в
нашем примере приведет к появлению ложных объектов Elvis. Чтобы предот­
вратить этот эффект, добавьте в класс spurious такой метод readResolve:
// Метод readResolve для сохранения свойства синглтона
private Object readResolve() {
// Возвращает истинный объект Elvis и позволяет
// сборщику мусора позаботиться о самозванце,
return INSTANCE;

Третий способ реализации синглтона состоит в объявлении одноэлементно­
го перечисления:
// Синглтон-перечисление — предпочтительный подход
public enum Elvis
{
INSTANCE;
public void leaveTheBuilding ()
{
)
}

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

46

ГЛАВА 2

СОЗДАНИЕ И УНИЧТОЖЕНИЕ ОБЪЕКТОВ

2.4. Обеспечивайте неинстанцируемость
с помощью закрытого конструктора
Иногда требуется написать класс, который представляет собой просто
сгруппированные статические методы и статические поля. Такие классы при­
обрели плохую репутацию, потому что некоторые программисты злоупотреб­
ляют ими, чтобы превратить объектно-ориентированное программирование в
процедурное, но они имеют и вполне законные применения. Они могут ис­
пользоваться для группирования связанных методов над примитивными зна­
чениями или массивами, как java. lang. Math или java .util .Arrays. Они
могут также использоваться для группирования статических методов, включая
фабрики (раздел 2.1), для объектов, реализующих некоторый интерфейс, на­
подобие java. util. Collections. (Начиная с Java 8 такие методы можно
поместить в интерфейс, в предположении, что вы можете его изменять.) Нако­
нец, такие классы могут использоваться для группирования методов в finalклассе, поскольку их нельзя поместить в подкласс.
Такие служебные классы не предназначены для создания экземпляров: их
экземпляры не имеют смысла. Однако в отсутствие явных конструкторов ком­
пилятор предоставляет открытый конструктор по умолчанию без параметров.
Для пользователя этот конструктор неотличим от любого другого. Не редкость
встретить непреднамеренное инстанцирование пользовательских классов в
опубликованных API.
Попытки запретить инстанцирование, делая класс абстрактным, не­
работоспособны. Кроме того, этот способ вводит в заблуждение пользова­
телей, считающих, что абстрактный класс предназначен для наследования
(раздел 4.5). Однако имеется очень простая идиома обеспечения неинстанцируемости. Конструктор по умолчанию создается, только если класс не содер­
жит явных конструкторов, так что класс можно сделать неинстанцируемым,
добавляя в него закрытый конструктор:
// Неинстанцируемый служебный класс
public class Utilityclass
(

// Подавление создания конструктора по умолчанию
// для достижения неинстанцируемости
private Utilityclass()
(
throw new AssertionError();
I
... // Остальной код опущен

2 5. ПРЕДПОЧИТАЙТЕ ВНЕДРЕНИЕ ЗАВИСИМОСТЕЙ ЖЕСТКО ПРОШИТЫМ РЕСУРСАМ

47

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

2.5. Предпочитайте внедрение зависимостей
жестко прошитым ресурсам
Многие классы зависят от одного или нескольких базовых ресурсов. Напри­
мер, средство проверки орфографии зависит от словаря. Не редкость — уви­
деть реализацию таких классов как статических (раздел 2.4):
// Ненадлежащее использование статического служебного
// класса — негибкое и не тестируемое!
public class Spellchecker
{
private static final Lexicon dictionary = ...;
private Spellchecker() {}
// Неинстанцируемый
public static boolean isValid(String word)
(

}
public static List suggestions(String typo)
(
)
)

Аналогично не редкость и реализация таких классов как синглтонов (раз­
дел 2.3):
// Ненадлежащее использование синглтона — негибкое и не
тестируемое!
public class Spellchecker
{
private final Lexicon dictionary = ...;
private Spellchecker(...) {}

48

ГЛАВА 2

СОЗДАНИЕ И УНИЧТОЖЕНИЕ ОБЪЕКТОВ

public static INSTANCE = new Spellchecker (...);
public boolean isValid(String word)
{

)
public List suggestions(String typo)
{

}

)

Ни один из этих подходов не является удовлетворительным, поскольку они
предполагают, что существует только один словарь, который стоит использо­
вать. На практике каждый язык имеет собственный словарь, а для специальных
целей могут использоваться специальные словари. Кроме того, может быть же­
лательно использовать специальный словарь для тестирования. Глупо считать,
что одного словаря будет достаточно всегда и для всех целей.
Вы могли бы попытаться обеспечить поддержку классом Spellchecker
нескольких словарей, сделав поле dictionary не окончательным и добавив
метод изменения словаря в существующем классе проверки орфографии, но
этот выход некрасивый, подверженный ошибкам и непригодный при исполь­
зовании параллелизма. Статические служебные классы и синглтоны непри­
годны для классов, поведение которых параметризовано лежащим в их
основе ресурсом.
Что на самом деле требуется — это возможность поддержки нескольких эк­
земпляров класса (в нашем примере — класса Spellchecker), каждый из ко­
торых использует необходимый клиенту ресурс (в нашем примере — словарь).
Простая схема, удовлетворяющая этому требованию — передача ресурса кон­
структору при создании нового экземпляра. Это одна из форм внедрения
зависимостей (dependency injection): словарь является зависимостью класса
проверки орфографии, которая внедряется в класс при его создании.
// Внедрение зависимостей обеспечивает гибкость и тестируемость
public class Spellchecker
(
private final Lexicon dictionary;

public Spellchecker(Lexicon dictionary)
{
this.dictionary = Objects.requireNonNull(dictionary);

}
public boolean isValid(String word)
(

)

2.5. ПРЕДПОЧИТАЙТЕ ВНЕДРЕНИЕ ЗАВИСИМОСТЕЙ ЖЕСТКО ПРОШИТЫМ РЕСУРСАМ

49

public List suggestions(String typo)

{
I

}

Схема внедрения зависимостей настолько проста, что многие программи­
сты годами используют ее, даже не подозревая, что она имеет собственное
имя. Хотя наш пример с проверкой орфографии использует только один ресурс
(словарь), внедрение зависимостей работает с произвольным количеством ре­
сурсов и произвольными графами зависимостей. Он сохраняет неизменность
классов (раздел 4.3), так что несколько клиентов могут совместно использовать
зависимые объекты (в предположении, что клиентам нужны одни и те же ба­
зовые ресурсы). Внедрение зависимостей в равной степени применимо к кон­
структорам, статическим фабрикам (раздел 2.1) и строителям (раздел 2.2).
Полезная разновидность схемы состоит в передаче конструктору фабрики
ресурсов. Фабрика — это объект, который может многократно вызываться для
создания экземпляров типа. Такие фабрики воплощают проектный шаблон Фа­
бричный метод [12]. Интерфейс Supplier, введенный в Java 8, идеально
подходит для представления фабрики. Методы, получающие Supplier в
качестве входных данных, обычно должны ограничивать параметр типа фабри­
ки с помощью ограниченного подстановочного типа (bounded wildcard type)
(раздел 5.6), чтобы позволить клиенту передать фабрику, которая создает любой
подтип указанного типа. Например, вот метод, который создает мозаику с ис­
пользованием клиентской фабрики для производства каждой плитки мозаики:
Mosaic create(Supplier