├── .gitignore ├── README.md ├── effective-java.iml ├── lib ├── lombok-1.18.24-javadoc.jar ├── lombok-1.18.24-sources.jar └── lombok-1.18.24.jar └── src ├── com └── zachesov │ └── effectivejava │ ├── chapter01 │ ├── item01 │ │ ├── StaticFactoryMethod.java │ │ └── Test.java │ ├── item02 │ │ ├── builder │ │ │ ├── NutritionFacts.java │ │ │ └── NutritionFactsLombok.java │ │ └── hierarchicalbuilder │ │ │ ├── Calzone.java │ │ │ ├── NyPizza.java │ │ │ ├── Pizza.java │ │ │ └── PizzaTest.java │ ├── item03 │ │ ├── SingletonEnum.java │ │ ├── SingletonField.java │ │ └── SingletonStaticFactory.java │ ├── item04 │ │ └── UtilityClass.java │ ├── item06 │ │ ├── RomanNumerals.java │ │ └── Sum.java │ └── item07 │ │ ├── EmptyStackException.java │ │ └── Stack.java │ ├── chapter06 │ └── item04 │ │ └── Plant.java │ ├── chapter07 │ └── item05 │ │ └── keytoelement │ │ ├── Album.java │ │ ├── Artist.java │ │ └── Main.java │ └── chapter11 │ └── item79 │ ├── ForwardingSet.java │ ├── ObservableSet.java │ ├── SetObserver.java │ ├── Test1.java │ ├── Test2.java │ └── Test3.java └── effective-java-3e-source-code /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | out 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Краткое содержание "JAVA. Эффективное программирование", 3-е издание, Джошуа Блох 2 | 3 | ## Описание 4 | 5 | Это мой конспект 6 | книги ["JAVA. Эффективное программирование", 3-е издание, Джошуа Блох](https://www.williamspublishing.com/Books/978-5-6041394-4-8.html) ( 7 | сайт российского издательства). 8 | 9 | [Репозиторий](https://github.com/jbloch/effective-java-3e-source-code/tree/master/src/effectivejava) с оригинальным 10 | кодом из книги. 11 | 12 | Если Вы автор и считаете, что данный конспект нарушает авторские права - прошу сообщить, я сделаю этот репозиторий 13 | приватным. 14 | 15 | Нашли опечатку/неточность? Пишите - разберемся. 16 | 17 | [Связаться со мной](https://t.me/szachesov) 18 | 19 | # 1. Содержание 20 | 21 | - [1. Содержание](#1-содержание) 22 | - [2. Создание и уничтожение объектов](#2-создание-и-уничтожение-объектов) 23 | - [2.1 Рассмотрите применение статических фабричных методов вместо конструкторов (Item 1)](#21-рассмотрите-применение-статических-фабричных-методов-вместо-конструкторов-item-1) 24 | - [2.2 При большом количестве параметров конструктора подумайте о проектном шаблоне Строитель (Item 2)](#22-при-большом-количестве-параметров-конструктора-подумайте-о-проектном-шаблоне-строитель-item-2) 25 | - [2.3 Получайте синглтон с помощью закрытого конструктора или типа перечисления (Item 3)](#23-получайте-синглтон-с-помощью-закрытого-конструктора-или-типа-перечисления-item-3) 26 | - [2.4 Обеспечивайте неинстанцируемость с помощью закрытого конструктора (Item 4)](#24-обеспечивайте-неинстанцируемость-с-помощью-закрытого-конструктора-item-4) 27 | - [2.5 Предпочитайте внедрение зависимостей жестко прошитым ресурсам (Item 5)](#25-предпочитайте-внедрение-зависимостей-жестко-прошитым-ресурсам-item-5) 28 | - [2.6 Избегайте создание излишних объектов (Item 6)](#26-избегайте-создание-излишних-объектов-item-6) 29 | - [2.7 Избегайте устаревших ссылок (Item 7)](#27-избегайте-устаревших-ссылок-item-7) 30 | - [2.8 Избегайте финализаторов и очистителей (Item 8)](#28-избегайте-финализаторов-и-очистителей-item-8) 31 | - [2.9 Предпочитайте try-with-recurse использованию try-finally (Item 9)](#29-предпочитайте-try-with-recurse-использованию-try-finally-item-9) 32 | - [3. Методы, общие для всех объектов](#3-методы-общие-для-всех-объектов) 33 | - [3.1 Перекрывая equals, соблюдайте общий контракт (Item 10)](#31-перекрывая-equals-соблюдайте-общий-контракт-item-10) 34 | - [3.2 Всегда при перекрытии equals перекрывайте hashCode (Item 11)](#32-всегда-при-перекрытии-equals-перекрывайте-hashcode-item-11) 35 | - [3.3 Всегда перекрывайте toString (Item 12)](#33-всегда-перекрывайте-tostring-item-12) 36 | - [3.4 Перекрывайте метод clone осторожно (Item 13)](#34-перекрывайте-метод-clone-осторожно-item-13) 37 | - [3.5 Подумайте о реализации Comparable (Item 14)](#35-подумайте-о-реализации-comparable-item-14) 38 | - [4 Классы и интерфейсы](#4-классы-и-интерфейсы) 39 | - [4.1 Минимизируйте доступность классов и интерфейсов (Item 15)](#41-минимизируйте-доступность-классов-и-интерфейсов-item-15) 40 | - [4.2 Используйте в открытых классах методы доступа, а не открытые поля (Item 16)](#42-используйте-в-открытых-классах-методы-доступа-а-не-открытые-поля-item-16) 41 | - [4.3 Минимизируйте изменяемость (Item 17)](#43-минимизируйте-изменяемость-item-17) 42 | - [4.4 Предпочитайте композицию наследованию (Item 18)](#44-предпочитайте-композицию-наследованию-item-18) 43 | - [4.5 Проектируйте и документируйте наследование, либо запрещайте его (Item 19)](#45-предпочитайте-композицию-наследованию-item-19-) 44 | - [4.6 Предпочитайте интерфейсы абстрактным классам (Item 20)](#46-предпочитайте-интерфейсы-абстрактным-классам-item-20) 45 | - [4.7 Проектируйте интерфейсы для потомков (Item 21)](#47-проектируйте-интерфейсы-для-потомков-item-21) 46 | - [4.8 Используйте интерфейсы только для определения типов (Item 22)](#48-используйте-интерфейсы-только-для-определения-типов-item-22) 47 | - [4.9 Предпочитайте иерархии классов дескрипторам классов (Item 23)](#49-предпочитайте-иерархии-классов-дескрипторам-классов-item-23) 48 | - [4.10 Предпочитайте статические классы-члены нестатическим (Item 24)](#410-предпочитайте-статические-классы-члены-нестатическим-item-24) 49 | - [4.11 Ограничивайтесь одним классом верхнего уровня на исходный файл (Item 25)](#411-ограничивайтесь-одним-классом-верхнего-уровня-на-исходный-файл-item-25) 50 | - [5 Обобщенное программирование](#5-обобщенное-программирование) 51 | - [5.1 Не используйте сырые типы (Item 26)](#51-не-используйте-сырые-типы-item-26) 52 | - [5.2 Устраняйте предупреждение о непроверяемом коде (Item 27)](#52-устраняйте-предупреждение-о-непроверяемом-коде-item-27) 53 | - [5.3 Предпочитайте списки массивам (Item 28)](#53-предпочитайте-списки-массивам-item-28) 54 | - [5.4 Предпочитайте обобщенные типы (Item 29)](#54-предпочитайте-обобщенные-типы-item-29) 55 | - [5.5 Предпочитайте обобщенные методы (Item 30)](#55-предпочитайте-обобщенные-методы-item-30) 56 | - [5.6 Используйте ограниченные символы подстановки для повышения гибкости API (Item 31)](#56-используйте-ограниченные-символы-подстановки-для-повышения-гибкости-api-item-31) 57 | - [5.7 Аккуратно сочетайте обобщенные типы и переменное количество аргументов (Item 32)](#57-аккуратно-сочетайте-обобщенные-типы-и-переменное-количество-аргументов-item-32) 58 | - [5.8 Применяйте безопасные с точки зрения типов гетерогенные контейнеры (Item 33)](#58-применяйте-безопасные-с-точки-зрения-типов-гетерогенные-контейнеры-item-33) 59 | - [6 Перечисления и аннотации](#6-перечисления-и-аннотации) 60 | - [6.1 Используйте перечисление вместо констант int (Item 34)](#61-используйте-перечисление-вместо-констант-int-item-34) 61 | - [6.2 Используйте поля экземпляра вместо порядковых значений (Item 35)](#62-используйте-поля-экземпляра-вместо-порядковых-значений-item-35) 62 | - [6.3 Используйте EnumSet вместо битовых полей (Item 36)](#63-используйте-enumset-вместо-битовых-полей-item-36) 63 | - [6.4 Используйте EnumMap вместо индексирования порядковыми номерами (Item 37)](#64-используйте-enummap-вместо-индексирования-порядковыми-номерами-item-37) 64 | - [6.5 Имитируйте расширяемые перечисления с помощью интерфейсов (Item 38)](#65-имитируйте-расширяемые-перечисления-с-помощью-интерфейсов-item-38) 65 | - [6.6 Предпочитайте аннотации схемам именования (Item 39)](#66-предпочитайте-аннотации-схемам-именования-item-39) 66 | - [6.7 Последовательно используйте аннотацию Override (Item 40)](#67-последовательно-используйте-аннотацию-override-item-40) 67 | - [6.8 Используйте интерфейсы-маркеры для определения типов (Item 41)](#68-используйте-интерфейсы-маркеры-для-определения-типов-item-41) 68 | - [7 Лямбда-выражения и Stream API](#7-лямбда-выражения-и-stream-api) 69 | - [7.1 Предпочитайте лямбда-выражения анонимным классам (Item 42)](#71-предпочитайте-лямбда-выражения-анонимным-классам-item-42) 70 | - [7.2 Предпочитайте ссылки на методы лямбда-выражениям (Item 43)](#72-предпочитайте-ссылки-на-методы-лямбда-выражениям-item-43) 71 | - [7.3 Предпочитайте использовать стандартные функциональные интерфейсы (Item 44)](#73-предпочитайте-использовать-стандартные-функциональные-интерфейсы-item-44) 72 | - [7.4 Разумно используйте Stream API (Item 45)](#74-разумно-используйте-stream-api-item-45) 73 | - [7.5 Предпочитайте в потоках функции без побочных эффектов (Item 46)](#75-предпочитайте-в-потоках-функции-без-побочных-эффектов-item-46) 74 | - [7.6 Предпочитайте коллекции потокам в качестве возвращаемых типов (Item 47)](#76-предпочитайте-коллекции-потокам-в-качестве-возвращаемых-типов-item-47) 75 | - [7.7 Будьте внимательны при многопоточном использовании Stream API (Item 48)](#77-будьте-внимательны-при-многопоточном-использовании-stream-api-item-48) 76 | - [8 Методы](#8-методы) 77 | - [8.1 Проверяйте корректность параметров (Item 49)](#81-проверяйте-корректность-параметров-item-49) 78 | - [8.2 При необходимости создавайте защитные копии (Item 50)](#82-при-необходимости-создавайте-защитные-копии-item-50) 79 | - [8.3 Тщательно проектируйте сигнатуры методов (Item 51)](#83-тщательно-проектируйте-сигнатуры-методов-item-51) 80 | - [8.4 Перезагружайте методы разумно (Item 52)](#84-перезагружайте-методы-разумно-item-52) 81 | - [8.5 Используйте методы с переменным количеством аргументов с осторожностью (Item 53)](#85-используйте-методы-с-переменным-количеством-аргументов-с-осторожностью-item-53) 82 | - [8.6 Возвращайте пустые массивы и коллекции, а не null (Item 54)](#86-возвращайте-пустые-массивы-и-коллекции-а-не-null-item-54) 83 | - [8.7 Возвращайте Optional с осторожностью (Item 55)](#87-возвращайте-optional-с-осторожностью-item-55) 84 | - [8.8 Пишите документирующие комментарии для всех открытых элементов API (Item 56)](#88-пишите-документирующие-комментарии-для-всех-открытых-элементов-api-item-56) 85 | - [9 Общие вопросы программирования](#9-общие-вопросы-программирования) 86 | - [9.1 Минимизируйте область видимости локальных переменных (Item 57)](#91-минимизируйте-область-видимости-локальных-переменных-item-57) 87 | - [9.2 Предпочитайте циклы for для коллекции традиционным циклам for (Item 58)](#92-предпочитайте-циклы-for-для-коллекции-традиционным-циклам-for-item-58) 88 | - [9.3 Изучите и используйте возможности библиотек (Item 59)](#93-изучите-и-используйте-возможности-библиотек-item-59) 89 | - [9.4 Если вам нужны точные ответы, избегайте float и double (Item 60)](#94-если-вам-нужны-точные-ответы-избегайте-float-и-double-item-60) 90 | - [9.5 Предпочитайте примитивные типы упакованных примитивным типам (Item 61)](#95-предпочитайте-примитивные-типы-упакованных-примитивным-типам-item-61) 91 | - [9.6 Избегайте применения строк там, где уместнее другой тип (Item 62)](#96-избегайте-применения-строк-там-где-уместнее-другой-тип-item-62) 92 | - [9.7 Помните о проблемах производительности при конкатенации строк (Item 63)](#97-помните-о-проблемах-производительности-при-конкатенации-строк-item-63) 93 | - [9.8 Для ссылки на объекты используйте их интерфейсы (Item 64)](#98-для-ссылки-на-объекты-используйте-их-интерфейсы-item-64) 94 | - [9.9 Предпочитайте интерфейсы рефлексии (Item 65)](#99-предпочитайте-интерфейсы-рефлексии-item-65) 95 | - [9.10 Пользуйтесь машинно-зависимыми методами осторожно (Item 66)](#910-пользуйтесь-машинно-зависимыми-методами-осторожно-item-66) 96 | - [9.11 Оптимизируйте осторожно (Item 67)](#911-оптимизируйте-осторожно-item-67) 97 | - [9.12 Придерживайтесь общепринятых соглашений по именованию (Item 68)](#912-придерживайтесь-общепринятых-соглашений-по-именованию-item-68) 98 | - [10 Исключения](#10-исключения) 99 | - [10.1 Используйте исключения только в исключительных ситуациях (Item 69)](#101-используйте-исключения-только-в-исключительных-ситуациях-item-69) 100 | - [10.2 Используйте для восстановления проверяемые исключения, а для программных ошибок - непроверяемые (Item 70)](#102-используйте-для-восстановления-проверяемые-исключения-а-для-программных-ошибок---непроверяемые-item-70) 101 | - [10.3 Избегайте ненужных проверяемых исключений (Item 71)](#103-избегайте-ненужных-проверяемых-исключений-item-71) 102 | - [10.4 Предпочитайте использовать стандартные исключения (Item 72)](#104-предпочитайте-использовать-стандартные-исключения-item-72) 103 | - [10.5 Генерируйте исключения, соответствующие абстракции (Item 73)](#105-генерируйте-исключения-соответствующие-абстракции-item-73) 104 | - [10.6 Документировать все исключения, которые может генерировать метод (Item 74)](#106-документировать-все-исключения-которые-может-генерировать-метод-item-74) 105 | - [10.7 Включайте в сообщения информацию о сбое (Item 75)](#107-включайте-в-сообщения-информацию-о-сбое-item-75) 106 | - [10.8 Добивайтесь атомарности сбоев (Item 76)](#108-добивайтесь-атомарности-сбоев-item-76) 107 | - [10.9 Не игнорируйте исключения (Item 77)](#109-не-игнорируйте-исключения-item-77) 108 | - [11 Многопоточное программирование](#11-многопоточное-программирование) 109 | - [11.1 Синхронизируйте доступ к совместно используемым изменяемым данным (Item 78)](#111-синхронизируйте-доступ-к-совместно-используемым-изменяемым-данным-item-78) 110 | - [11.2 Избегайте излишней синхронизации (Item 79)](#112-избегайте-излишней-синхронизации-item-79) 111 | - [11.3 Предпочитайте исполнителей, задания и потоки данных потокам исполнения (Item 80)](#113-предпочитайте-исполнителей-задания-и-потоки-данных-потокам-исполнения-item-80) 112 | - [11.4 Предпочитайте утилиты параллельности методам wait и notify(Item 81)](#114-предпочитайте-утилиты-параллельности-методам-wait-и-notify-item-81) 113 | - [11.5 Документируйте безопасность с точки зрения потоков (Item 82)](#115-документируйте-безопасность-с-точки-зрения-потоков-item-82) 114 | - [11.6 Аккуратно применяйте отложенную инициализацию (Item 83)](#116-аккуратно-применяйте-отложенную-инициализацию-item-83) 115 | - [11.7 Избегайте зависимости от планировщика потоков (Item 84)](#117-избегайте-зависимости-от-планировщика-потоков-item-84) 116 | - [12 Сериализация](#12-сериализация) 117 | - [12.1 Предпочитайте альтернативы сериализации Java (Item 85)](#121-предпочитайте-альтернативы-сериализации-java-item-85) 118 | - [12.2 Реализуйте интерфейс Serializable крайне осторожно (Item 86)](#122-реализуйте-интерфейс-serializable-крайне-осторожно-item-86) 119 | - [12.3 Подумайте о применении пользовательской сериализованной формы (Item 87)](#123-подумайте-о-применении-пользовательской-сериализованной-формы-item-87) 120 | - [12.4 Создавайте защищенные методы readObject (Item 88)](#124-создавайте-защищенные-методы-readobject-item-88) 121 | - [12.5 Для управления экземпляром предпочитайте типы перечислений методу readResolve (Item 89)](#125-для-управления-экземпляром-предпочитайте-типы-перечислений-методу-readresolve-item-89) 122 | - [12.6 Подумайте о применении прокси-агента сериализации вместо сериализованных экземпляров (Item 90)](#126-подумайте-о-применении-прокси-агента-сериализации-вместо-сериализованных-экземпляров-item-90) 123 | 124 | # 2. Создание и уничтожение объектов 125 | 126 | ## 2.1 Рассмотрите применение статических фабричных методов вместо конструкторов (Item 1) 127 | 128 | Плюсы: 129 | 130 | - Они имеют имена 131 | - Они не обязаны создавать новые объекты при каждом вызове 132 | - Могу возвращать объект любого подтипа их возвращаемого типа 133 | - Класс возращенного объекта может варьироваться от вызова к вызову в зависимости от входных параметров 134 | - Класс возвращаемого объекта не обязан существовать во время разработки класса, содержащего метод 135 | 136 | Минусы: 137 | 138 | - Классы без открытых или защищенных конструкторов не могу порождать подклассы 139 | - Трудно отличить от других статических методов 140 | 141 | Распространенные статистические фабричные методы: 142 | 143 | | **Название** | **Описание** | **Пример** | 144 | |-------------------------------|--------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------| 145 | | **from** | преобразование типа | `Date d = Date.from(instant)` | 146 | | **of** | метод агрегации | `Set faceCards = EnumSet.of(JACK, QUEEN, KING)` | 147 | | **valueOf** | многословная альтернатива **from**, **of** | `BigInteger prime = BigInteger.valueOf(Integer.MAX_VALUE)` | 148 | | **instance**, **getInstance** | возвращает экземпляр, описываемый параметрами, но о котором нельзя сказать, что имеет тоже значение | `StackWalker luke = StackWalker.getInstance(options)` | 149 | | **create**, **newInstance** | подобен **instance** или **getInstance**, но точно возвращает новый экземпляр | `Object newArray = Array.newInstance(classObject, arrayLen)` | 150 | | **get**Type | подобен **getInstance**, но если фабричный метод находится в другом классе. Type - тип возвращаемого объекта | `FileStore fs = Files.getFileStore(path)` | 151 | | **new**Type | подобен **newInstance**, но если фабричный метод находится в другом классе. Type - тип возвращаемого объекта | `BufferedReader br = Files.newBufferedReader(path)` | 152 | | type | краткая альтернатива **get**Type и **new**Type | `List litany = Collections.list(legacyLitany)` | 153 | 154 | ## 2.2 При большом количестве параметров конструктора подумайте о проектном шаблоне Строитель (Item 2) 155 | 156 | - Если конструктор или статические фабрики имеет большое количество параметров(4+), то лучше рассмотреть шаблон 157 | Строитель при создании объекта 158 | - Код клиента проще для чтения и записи 159 | - Безопаснее чем шаблон `JavaBeans` и может быть неизменяемым 160 | 161 | _Реализация_: 162 | 163 | ```java 164 | 165 | public class NutritionFacts { 166 | private final int servingSize; 167 | private final int servings; 168 | private final int calories; 169 | private final int fat; 170 | private final int sodium; 171 | private final int carbohydrate; 172 | 173 | public static class Builder { 174 | // Обязательные параметры 175 | private final int servingSize; 176 | private final int servings; 177 | 178 | // Опциональные параметры со значением по умолчанию 179 | private int calories = 0; 180 | private int fat = 0; 181 | private int carbohydrate = 0; 182 | private int sodium = 0; 183 | 184 | public Builder(int servingSize, int servings) { 185 | this.servingSize = servingSize; 186 | this.servings = servings; 187 | } 188 | 189 | public Builder calories(int val) { 190 | calories = val; 191 | return this; 192 | } 193 | 194 | public Builder fat(int val) { 195 | fat = val; 196 | return this; 197 | } 198 | 199 | public Builder carbohydrate(int val) { 200 | carbohydrate = val; 201 | return this; 202 | } 203 | 204 | public Builder sodium(int val) { 205 | sodium = val; 206 | return this; 207 | } 208 | 209 | public NutritionFacts build() { 210 | return new NutritionFacts(this); 211 | } 212 | } 213 | 214 | private NutritionFacts(Builder builder) { 215 | servingSize = builder.servingSize; 216 | servings = builder.servings; 217 | calories = builder.calories; 218 | fat = builder.fat; 219 | sodium = builder.sodium; 220 | carbohydrate = builder.carbohydrate; 221 | } 222 | } 223 | ``` 224 | 225 | _Код клиента:_ 226 | `NutritionFacts cocaCola=new NutritionFacts.Builder(240,8).calories(100).sodium(35).carbohydrate(27).build();` 227 | 228 | ## 2.3 Получайте синглтон с помощью закрытого конструктора или типа перечисления (Item 3) 229 | 230 | Синглтон с полем `public final`: 231 | 232 | ```java 233 | public class Elvis { 234 | public static final Elvis INSTANCE = new Elvis(); 235 | 236 | private Elvis() { 237 | } 238 | 239 | public void singASong() { 240 | } 241 | } 242 | 243 | ``` 244 | 245 | Плюсы: 246 | 247 | - Из-за `public` поля API делает очевидным, что объект будет синглтом 248 | - Простая реализация 249 | 250 | _Синглтон со статической фабрикой:_ 251 | 252 | ```java 253 | public class Elvis { 254 | private static final Elvis INSTANCE = new Elvis(); 255 | 256 | private Elvis() { 257 | } 258 | 259 | public static Elvis getInstance() { 260 | return INSTANCE; 261 | } 262 | 263 | public void singASong() { 264 | } 265 | } 266 | 267 | ``` 268 | 269 | Плюсы: 270 | 271 | - Можно изменить класс на не синглтон, не меняя его API 272 | - Можно написать обобщенную фабрику синглтонов 273 | - Можно использовать ссылку на метод, при использовании лямбды-выражения 274 | 275 | Минусы этих реализаций: 276 | 277 | - Можно вызвать конструктор с помощью рефлексии. Защита - добавить в конструктор генерацию исключения, при 278 | создании второго экземпляра. 279 | 280 | Что бы сделать эти классы сереализуемым, необходимо пометить все поля `transient` и предоставить 281 | метод `readResolve` ([Item 89](#125-для-управления-экземпляром-предпочитайте-типы-перечислений-методу-readresolve-item-89)). 282 | 283 | ```java 284 | class Item3 { 285 | private Object readResolve() { 286 | // Возвращает истинный объект 287 | return INSTANCE; 288 | } 289 | } 290 | 291 | ``` 292 | 293 | Синглтон-перечисление: 294 | 295 | ```java 296 | 297 | public enum Elvis() { 298 | INSTANCE; 299 | 300 | public void singASong() { 301 | } 302 | } 303 | 304 | ``` 305 | 306 | Самый компактный способ, но не поддерживается наследование от класса, только реализация интерфейса. 307 | 308 | ## 2.4 Обеспечивайте неинстанцируемость с помощью закрытого конструктора (Item 4) 309 | 310 | Это подходит для классов, который имеет только статистические методы и статистические поля (утильные классы). 311 | 312 | Например: 313 | 314 | - Группировка связанных методов над примитивами или массивами (`java.util.Arrays`) 315 | - Группировка статистических методов и фабрики (`java.util.Collections`) 316 | - Группировка методов в `final` - классе 317 | 318 | ```java 319 | public class UtilityClass { 320 | private UtilityClass() { 321 | throw new AssertionError(); 322 | } 323 | } 324 | 325 | ``` 326 | 327 | ## 2.5 Предпочитайте внедрение зависимостей жестко прошитым ресурсам (Item 5) 328 | 329 | Не стоит использовать класс синглтон или утильный(статистический), если они зависят от ресурсов(объектов), поведение 330 | которых влияют на них. И не стоит предоставлять создание ресурсов этим классам. 331 | Лучше предавать ресурсы или их фабрики для создания, конструктору (или статистической фабрике, или строителю). Этот 332 | подход называется **Внедрение зависимостей**, повышает гибкость, возможность повторного использования. 333 | 334 | ```java 335 | public class SpellChecker { 336 | private final Lexicon dictionary; 337 | 338 | public SpellChecker(Lexicon dictionary) { 339 | this.dictionary = Objects.requireNonNull(dictionary); 340 | } 341 | } 342 | ``` 343 | 344 | ## 2.6 Избегайте создание излишних объектов (Item 6) 345 | 346 | 1. Повторное использование неизменяемых объектов(пулл) 347 | 348 | ```java 349 | class StringPool { 350 | void pool() { 351 | String n = new String("bikini"); // Плохо 352 | String p = "bikini"; // Хорошо 353 | } 354 | } 355 | ``` 356 | 357 | При повторном использовании, в первом случае будет создаваться новый объект, во втором использоваться старый из пула. 358 | 359 | Использование статических фабричных методов для неизменяемого 360 | объекта ([Item 3](#23-получайте-синглтон-с-помощью-закрытого-конструктора-или-типа-перечисления-item-3)): 361 | 362 | `Boolean.valueOf(String)` 363 | 364 | 2. Кэширование тяжеловесных объектов 365 | 366 | ```java 367 | class RomanNumerals { 368 | // Можно повысить производительность 369 | static boolean isRomanNumeralSlow(String s) { 370 | return s.matches("^(?=.)M*(C[MD]|D?C{0,3})" + "(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$"); 371 | } 372 | } 373 | 374 | ``` 375 | 376 | ```java 377 | // Оптимально 378 | class Item6 { 379 | private static final Pattern ROMAN = 380 | Pattern.compile("^(?=.)M*(C[MD]|D?C{0,3})" + "(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$"); 381 | 382 | static boolean isRomanNumeralFast(String s) { 383 | return ROMAN.matcher(s).matches(); 384 | } 385 | } 386 | 387 | ``` 388 | 389 | 3. Предпочитайте примитивы классам оберткам и следите за непреднамеренной автоматической упаковкой 390 | 391 | Но учтите: 392 | 393 | - Не следует кэшировать легковесные объекты 394 | - Избегайте создания собственного пула объектов. За исключением для тяжеловесных объектов, например подключение к базе 395 | данных 396 | 397 | ## 2.7 Избегайте устаревших ссылок (Item 7) 398 | 399 | Лучший способ устранить устаревшие ссылки - выход ссылочной переменной из области видимости. 400 | 401 | Источники утечки памяти: 402 | 403 | - **Класс управляет своей памятью.** В данном случае подойдет обнуление ссылки. 404 | 405 | ```java 406 | class Item7 { 407 | public pop() { 408 | if (size == 0) throw new EmptyStackException(); 409 | Object result = elements[--size]; 410 | elements[size] = null; 411 | return result; 412 | } 413 | } 414 | 415 | ``` 416 | 417 | Обнуление ссылки не норма, а исключение из правил - не нужно обнулять ссылку для каждого объекта. 418 | 419 | - **Утечка памяти в кэше.** 420 | Для очищения ссылок из кэша, можно использовать следующие решения. 421 | Использовать `WeakHashMap`, но только если желаемое время жизни записей кэша определяется внешним ссылками на ключ, а 422 | не значением. Можно очищать в кэше старые записи: использовать фоновые потоки(`ScheduledThreadPoolExecutor`), удаление 423 | старых ссылок после вставки новой или `LinkedHashMap` с методом `removeEldestEntry`. 424 | - **Приложения в режиме ожидания(слушателя) и другие обратные вызовы.** Если клиенты регистрируют обратные вызовы, но 425 | позже не отменяют эту регистрацию, они могут накапливаться. Можно хранить на них только слабые ссылки используя 426 | `WeakHashMap`. 427 | 428 | Утечки памяти могут накапливаться в системе годами. И обнаружить их можно тщательного ревю кода или/и с использованием 429 | профилировщика. 430 | 431 | ## 2.8 Избегайте финализаторов и очистителей (Item 8) 432 | 433 | - Финализаторы непредсказуемы, часто опасны и в общем случае не нужны 434 | - Очистители (Java 9+) менее опасны, чем финализаторы, но столь же непредсказуемые, медленные и, в общем случае, 435 | ненужные 436 | - С помощью финализатора или очистителя нельзя выполнять никакие операции, критичные по времени исполнения 437 | - Никогда не следует обновлять состояние объекта в зависимости от финализатора или очистителя 438 | - Серьезные проблемы производительности при использовании финализаторов или очистителей 439 | - Финализаторы являются серьезной проблемой безопасности: они открывают ваш класс для атак финализаторов 440 | - Генерация исключения в конструкторе должно быть достаточно для предотвращения существования объекта; однако при 441 | наличии финализатора это не так 442 | - Для защиты классов, не являющихся финальными, от атак финализаторов напишите метод `finalize`, который не выполняет 443 | никаких действий 444 | - Для класса, который инкапсулирует ресурсы, требующие освобождения, например файлы или потоки, лучше сделать его 445 | реализующий `AutoCloseable`. После этого его можно применять в 446 | конструкции `try-with-recurse` ([Item 9](#29-предпочитайте-try-with-recurse-использованию-try-finally-item-9)) 447 | 448 | ## 2.9 Предпочитайте try-with-recurse использованию try-finally (Item 9) 449 | 450 | Предпочитайте `try-with-recurse` применению `try-finally` при работе с ресурсами, которые должны быть закрыты. 451 | Результирующий код получается короче и понятнее, а исключения, которые он генерирует, — более полезными. 452 | 453 | ```java 454 | class Item9 { 455 | static void copy(String src, String dst) throws IOException { 456 | try (InputStream in = new InputStream(src); 457 | OutputStream out = new FileOutputStream(dst)) { 458 | byte[] buf = new byte[BUFFER_SIZE]; 459 | int n; 460 | while ((n = in.read(buf)) >= 0) { 461 | out.write(buf, 0, n); 462 | } 463 | } 464 | } 465 | } 466 | 467 | ``` 468 | 469 | Оператор `try-with-recurse` облегчает написание корректного кода с использованием ресурсов, которые должны быть закрыты, 470 | что практически невозможно с помощью `try-finally`. 471 | 472 | # 3. Методы, общие для всех объектов 473 | 474 | ## 3.1 Перекрывая equals, соблюдайте общий контракт (Item 10) 475 | 476 | Когда не следует перекрывать: 477 | 478 | - Каждый экземпляр класса уникален по своей природе 479 | - У класса нет необходимости в проверке "логической эквивалентности" 480 | - Суперкласс уже переопределяет `equals`, и поведение суперкласса подходит для данного класса 481 | - Класс является закрытым или закрытым на уровне пакета, и вы уверены, что его метод `equals` никогда не будет 482 | вызываться 483 | 484 | Класс имеет смысл перекрывать `equals`, когда для него определено понятие логической эквивалентности, которая не 485 | совпадает с тождественностью объектов, а метод `equals` в суперклассе не перекрыт. Это происходит с классами 486 | значениями(value class) - `String`, `Integer` и др. 487 | 488 | Требования отношения эквивалентности: 489 | 490 | - Рефлексивность: `x.equals(x)==true` 491 | - Симметричность: `x.equals(y)==y.equals(x)` 492 | - Транзитивность: `x.equals(y)==y.equals(z)==z.equals(x)` 493 | - Непротиворечивость: `x.equals(y)==x.equals(y)==x.equals(y)==...` 494 | - Отличный от `null`: `x.equals(null)==false` 495 | 496 | Рецепт хорошего `equals`: 497 | 498 | - Используйте оператор `==` для проверки того, что аргумент является ссылкой на данный объект 499 | - Используйте оператор `instanceof` для проверки того, что аргумент имеет корректный тип 500 | - Приводите аргумент к корректному типу 501 | - Для каждого "важного" поля класса убедитесь, что значение этого поля в аргументе соответствует полю данного объекта 502 | 503 | ```java 504 | class Item10 { 505 | @Override 506 | public boolean equals(Object o) { 507 | if (o == this) return true; 508 | 509 | if (!(o instanceof PhoneNumber)) return false; 510 | 511 | PhoneNumber pn = (PhoneNumber) o; 512 | return pn.lineNumber == lineNumber && pn.prefix == prefix && pn.areaCode == areaCode; 513 | } 514 | } 515 | 516 | ``` 517 | 518 | Ещё предостережения: 519 | 520 | - Всегда перекрывайте `hashCode` при перекрытии `equals` 521 | - Не пытайтесь быть слишком умным 522 | - Не подставляйте другой тип вместо `Object` в объявлении `equals` 523 | 524 | ## 3.2 Всегда при перекрытии equals перекрывайте hashCode (Item 11) 525 | 526 | Вы обязанный перекрывать `hashCode` в каждом классе, перекрывающем `equals`. 527 | 528 | Контракт `hashCode`: 529 | 530 | - Для одно объекта, при многократном вызове `hashCode` должен всегда возвращать одно и то же целое число. При условии, 531 | что никакая информация, используемая при сравнении этого объекта с другими методом `equals`, не изменилась. 532 | - Если два объекта равны по `equals`, то по `hashCode` тоже должны быть равны 533 | - Если два объекта неравны по `equals`, то по `hashCode` могут быть равны или не равны. Но для оптимизации, лучше что бы 534 | были равны 535 | 536 | Рецепт хорошего `hashCode`: 537 | 538 | 1. Объявить переменную `int result` и инициализировать её хеш-кодом для первого значащего поля объекта, описано в п. 2( 539 | а) 540 | 2. Для остальных значащих полей: 541 | 542 | * Вычислите хеш-код типа `int` следующим образом: 543 | * Примитивный тип: `Type.hashCode(f)`, Type - клас обертка для примитивного типа 544 | * Ссылочный тип: если метод `equal` перекрыт и рекурсивно вызывается при использовании, то и `hashCode` вызывать 545 | рекурсивно. Если требуется сложное сравнение, то вычислить "каноническое представление" и вызвать `hashCode`. Если 546 | `null`, то 0. 547 | * Массив: вычислить для каждого объекта рекурсивно `hashCode` и объединить, а как в след. пункте. Если нет значащих 548 | элементов, то значение 0. Если все значащиеся, то `Arrays.hasCode`. 549 | * Объедините хеш-код, вычисленные в предыдущем пункте: `result = 31 * result + с(предыдущий hashCode)`. 550 | 551 | 3. Верните `result` 552 | 553 | ```java 554 | class Item11 { 555 | @Override 556 | public int hashCode() { 557 | int result = Short.hashCode(areaCode); 558 | result = 31 * result + Short.hashCode(prefix); 559 | result = 31 * result + Short.hashCode(lineNum); 560 | return result; 561 | } 562 | } 563 | 564 | ``` 565 | 566 | И ещё: 567 | 568 | - Не пытайтесь исключить значимые поля из вычисления хеш-кода для повышения производительности 569 | - Не предоставляйте подробную спецификацию значения, возвращаемого `hashCode`, так, чтобы клиенты не могли от него 570 | зависеть; это позволит его изменить. 571 | 572 | ## 3.3 Всегда перекрывайте toString (Item 12) 573 | 574 | Всегда перекрывайте метод `toString` в каждом инстансированном классе, если только он не переопределен в суперклассе. 575 | 576 | Предоставление хорошей реализации метода `toString` делает ваш класс гораздо более удобным в использовании, а 577 | использующею его систему - более простой в отладке. 578 | 579 | Чтобы представлять интерес на практике, метод `toString` должен возвращать всю полезную информацию, которая содержится в 580 | объекте. 581 | 582 | Документируйте логику вывода `toString`, если даже не стандартизируете формат вывода. 583 | 584 | Предоставляйте программный доступ ко всей информации в значении, возвращаемом методом `toString`. 585 | 586 | ## 3.4 Перекрывайте метод clone осторожно (Item 13) 587 | 588 | Класс, реализующий `Cloneable`, предоставляет надлежащим образом функционирующий открытый метод `clone`. Иначе при 589 | вызове клон возвратится `CloneNotSupportedException`. 590 | 591 | Неизменяемые классы никогда не должны предоставлять метод `clone`, потому что это будет просто поощрением излишнего 592 | копирования. 593 | 594 | ```java 595 | class PhoneNumber implements Cloneable { 596 | // Clone method for class with no references to mutable state 597 | @Override 598 | public PhoneNumber clone() { 599 | try { 600 | return (PhoneNumber) super.clone(); 601 | } catch (CloneNotSupportedException e) { 602 | throw new AssertionError(); // Can't happen 603 | } 604 | } 605 | } 606 | 607 | ``` 608 | 609 | Если класс имеет поля, которые ссылаются на изменяемые объекты, то нужно вызвать `clone` рекурсивно для изменяемых 610 | полей. Иначе поля клона будут ссылаться на объекты, на которые ссылаются поля объекта оригинала. 611 | 612 | ```java 613 | class Stack implements Cloneable { 614 | @Override 615 | public Stack clone() { 616 | try { 617 | Stack result = (Stack) super.clone(); 618 | result.elements = elements.clone(); 619 | return result; 620 | } catch (CloneNotSupportedException e) { 621 | throw new AssertionError(); 622 | } 623 | } 624 | } 625 | 626 | ``` 627 | 628 | Архитектура `Cloneable` несовместима с нормальны использованием `final` - полей, ссылающихся на изменяемые объекты. 629 | Возможно потребуется убрать `final`, что клонировать объект. 630 | 631 | Метод `clone` никогда не должен вызывать перекрываемый метод для создаваемого клона. 632 | 633 | Открытые методы `clone` должны опускать конструкцию `throws`, поскольку методы, не генерирующие проверяем исключения, 634 | более просты в использовании. 635 | 636 | При проектировании класса для наследования, он не должен реализовывать интерфейс `Cloneable`. Есть два варианта: 637 | имитировать поведение `Object`, путем реализации корректного `clone` или как генерирующего исключение 638 | `CloneNotSupportedException`. 639 | 640 | Метод клонирования должен правильно быть синхронизирован. 641 | 642 | Лучшие альтернативы для клонирования объектов: 643 | 644 | 1. Конструктор копирования 645 | `public Yum(Yum yum)` 646 | 647 | 2. Фабрика копий 648 | `public static Yum newInstance(Yum yum)` 649 | 650 | Плюсы: 651 | 652 | - Не полагается на сложный механизм создания объектов 653 | - Не требует соблюдения слабо документированных соглашений 654 | - Не конфликтует с использованием `final` - полей 655 | - Не генерирует ненужные непроверяемые исключения 656 | - Не требует преобразования объектов 657 | 658 | Также данные подходы позволяют использовать для преобразования объектов `new TreeMap<>(new HashMap<>())` 659 | 660 | ## 3.5 Подумайте о реализации Comparable (Item 14) 661 | 662 | Реализуя класс значения(value class), которое имеет упорядочение, обеспечивайте реализацию `Comparable`, чтобы его 663 | экземпляры можно было легко сортировать, искать и использовать в коллекциях, основных на сравнениях. 664 | 665 | Контракт метода `compareTo`: 666 | 667 | - `x.compareTo(y) == - (y.compareTo(x))` 668 | - если `x.compareTo(y) > 0 && y.compareTo(z) > 0`, тогда и `x.compareTo(z) > 0` 669 | - если `x.compareTo(y) == 0`, тогда и `x.compareTo(z) == y.compareTo(z)` 670 | - Рекомендуется, но не обязательно: `(x.compareTo(y)==0) == (x.equals(y))` 671 | 672 | Если метод `compareTo` сталкивается с объектами разных типов, генерируйте исключение `ClassCastException`. 673 | 674 | При сравнении значимых полей в `compareTo` следует избегать операторы `<` и `>`. Вместо этого использовать 675 | статистические методы `compare` классов оберток примитивных типов(`Integer.compare` и др.) или методы конструирования 676 | компаратора в интерфейсе`Comparator`(`Comparator.comparingInt` и др.). 677 | 678 | # 4 Классы и интерфейсы 679 | 680 | ## 4.1 Минимизируйте доступность классов и интерфейсов (Item 15) 681 | 682 | Хорошо спроектированный модуль скрывает все детали реализации, четко отделяя API от реализации. 683 | 684 | Делайте класс или член класса как можно более недоступным. 685 | 686 | Классы и интерфейсы верхнего уровня могу иметь два уровня доступа: по-умолчанию и открытые. 687 | Если класс или интерфейс верхнего уровня, доступный лишь в пределах пакета и используется только в одном классе, нужно 688 | рассмотреть превращения его в закрытый статический класс, вложенный только в тот класс, в котором он 689 | используется ([Item 24](#410-предпочитайте-вложенный-статический-класс-вложенному-внутреннему-классу-item-24)). 690 | 691 | Если какой-либо метод перекрывает метод суперкласса, то в подклассе он не может иметь более ограниченный доступ, чем в 692 | суперклассе. 693 | 694 | Поля класса должны иметь уровень доступа `private`. 695 | Поля константы могут быть открыты `public static final`. Но если константа ссылается на изменяемый объект, то поле 696 | обладает недостатками не `final` поля. 697 | 698 | Массив ненулевой длинный всегда изменяемый, ошибка если объявлен как `public static final`. 699 | 700 | В Java 9+ появилось два неявных модификатора, как часть модульной системы. Открытые и защищенные члены открытых классов 701 | в неэкспортируемых пакетах дают два неявных уровня доступа, которые являются внутри модульными аналогами обычных 702 | открытых и защищенных уровней. Данный модификаторы не так распространены и можно обойтись переупорядочиванием классов 703 | внутри 704 | пакета. 705 | 706 | ## 4.2 Используйте в открытых классах методы доступа, а не открытые поля (Item 16) 707 | 708 | Минусы открытых полей: 709 | 710 | - Они лишены преимуществ инкапсуляции 711 | - Нельзя изменить представление класса, не изменив его API 712 | - Нельзя обеспечить выполнение инвариантов и предпринимать дополнительных действий при обращении к полю 713 | 714 | Если класс доступен за пределами пакета, следует обеспечить методы доступа. Но если класс доступен в пределах пакета или 715 | является закрытым вложенным классом, то никакого ущерба от предоставления доступа к его полям данных не будет. 716 | 717 | ## 4.3 Минимизируйте изменяемость (Item 17) 718 | 719 | Неизменяемый класс - это класс, вся информация которого записывается в момент создания объекта и остается неизменной в 720 | течение всего времени существования. Их проще проектировать, реализовывать и использовать. Они менее подвержены ошибкам 721 | и более безопасны. 722 | 723 | Как сделать класс незаменяемым: 724 | 725 | 1. Не проставлять методы, которые изменяют состояние объекта - методы установки(mutator). 726 | 2. Гарантируйте невозможность расширения класса. Добавление классу ключевого слова `final`, есть и другие способы. 727 | 3. Объявите все поля как `final` 728 | 4. Объявите все поля как `private` 729 | 5. Обеспечьте монопольный доступ ко всем изменяемым компонентам. Если класс имеет поля, ссылающиеся на изменяемые 730 | объекты, клиенты класса не должны получить ссылки на эти объекты. Не инициализируйте эти поля ссылками на объект, 731 | которые предоставляют клиент. Делайте защитные 732 | копии ([Item 50](#82-при-необходимости-создавайте-защитные-копии-item-50)) в конструктора, методах доступа и 733 | методах `readObject` ([Item 88](#124-создавайте-защищенные-методы-readobject-item-88)). 734 | 735 | ```java 736 | public final class Complex { 737 | private final double re; 738 | private final double im; 739 | 740 | public Complex(double re, double im) { 741 | this.re = re; 742 | this.im = im; 743 | } 744 | 745 | // Accessors with no corresponding mutators 746 | public double realPart() { 747 | return re; 748 | } 749 | 750 | public double imaginaryPart() { 751 | return im; 752 | } 753 | 754 | public Complex add(Complex c) { 755 | return new Complex(re + c.re, im + c.im); 756 | } 757 | 758 | public Complex subtract(Complex c) { 759 | return new Complex(re - c.re, im - c.im); 760 | } 761 | 762 | @Override 763 | public boolean equals(Object o) { 764 | } 765 | } 766 | 767 | ``` 768 | 769 | Особенности неизменяемого класса: 770 | 771 | - Неизменяемые объекты просты. Они могут находиться только в одном состоянии - с котором был создан. 772 | - Неизменяемые объекты потокобезопасны и не нужна синхронизация. 773 | - Неизменяемые объекты можно использоваться совместно. 774 | - Можно создавать неизменяемые объекты через статическую 775 | фабрику ([Item 1](#21-рассмотрите-применение-статических-фабричных-методов-вместо-конструкторов-item-1)) и кешировать 776 | часто запрашиваемые объекты. 777 | - Можно совместно использовать не только неизменяемые объекты, но и их внутреннее представление. 778 | - Неизменяемые объекты образуют крупные строительные блоки для прочих объектов. 779 | - Неизменяемые объекты обеспечивают атомарность. 780 | 781 | Основным недостатком неизменяемых классов является то, они требуют отдельных объектов для каждого уникального значения. 782 | И следствие снижение производительности. 783 | 784 | Запретить наследование, помимо `final`, можно сделать его конструктор закрытым или доступным на уровне пакета и добавить 785 | статистическую фабрику ([Item 1](#21-рассмотрите-применение-статических-фабричных-методов-вместо-конструкторов-item-1)). 786 | 787 | ```java 788 | public class Complex { 789 | private final double re; 790 | private final double im; 791 | 792 | private Complex(double re, double im) { 793 | this.re = re; 794 | this.im = im; 795 | } 796 | 797 | public static Complex valueOf(double re, double im) { 798 | return new Complex(re, im); 799 | } 800 | } 801 | 802 | ``` 803 | 804 | Данный подход позволяет использовать несколько классов реализации, доступных в пределах пакета. За пределами пакета, 805 | неизменяемый класс является финальным. 806 | 807 | Если класс невозможно сделать неизменяемым, нужно максимально ограничить изменяемость и видимость. 808 | 809 | ## 4.4 Предпочитайте композицию наследованию (Item 18) 810 | 811 | Речь идет только о наследовании от другого класса, а не о реализации интерфейса. 812 | 813 | Наследование нарушает инкапсуляцию: 814 | 815 | - Реализация суперкласса может меняться от версии к версии, и, если это происходит, 816 | подкласс может перестать корректно работать, даже если его код останется нетронутым. 817 | - В новых версиях суперкласса может появиться новый метод, который не будет учитываться в подклассах и может привести 818 | проблемы с безопасностью. 819 | 820 | Композиция как альтернатива наследованию. В классе создать закрытое поле, которое будет содержать ссылку на экземпляр 821 | существующего класса. Каждый метод экземпляра в новом классе вызывает соответсвующий метод содержащего в классе 822 | экземпляра существующего класса, а затем возвращает полученный результат. Эта технология передачи (forwarding), а 823 | методы - методы передачи (forwarding methods). 824 | 825 | ```java 826 | // Wrapper class - uses composition in place of inheritance 827 | public class InstrumentedSet extends ForwardingSet { 828 | private int addCount = 0; 829 | 830 | public InstrumentedSet(Set s) { 831 | super(s); 832 | } 833 | 834 | @Override 835 | public boolean add(E e) { 836 | addCount++; 837 | return super.add(e); 838 | } 839 | 840 | @Override 841 | public boolean addAll(Collection c) { 842 | addCount += c.size(); 843 | return super.addAll(c); 844 | } 845 | 846 | public int getAddCount() { 847 | return addCount; 848 | } 849 | } 850 | 851 | // Reusable forwarding class 852 | public class ForwardingSet implements Set { 853 | private final Set s; 854 | 855 | public ForwardingSet(Set s) { 856 | this.s = s; 857 | } 858 | 859 | public void clear() { 860 | s.clear(); 861 | } 862 | 863 | public boolean contains(Object o) { 864 | return s.contains(o); 865 | } 866 | 867 | public boolean isEmpty() { 868 | return s.isEmpty(); 869 | } 870 | 871 | public int size() { 872 | return s.size(); 873 | } 874 | 875 | public Iterator iterator() { 876 | return s.iterator(); 877 | } 878 | 879 | public boolean add(E e) { 880 | return s.add(e); 881 | } 882 | 883 | public boolean remove(Object o) { 884 | return s.remove(o); 885 | } 886 | 887 | public boolean containsAll(Collection c) { 888 | return s.containsAll(c); 889 | } 890 | 891 | public boolean addAll(Collection c) { 892 | return s.addAll(c); 893 | } 894 | 895 | public boolean removeAll(Collection c) { 896 | return s.removeAll(c); 897 | } 898 | 899 | public boolean retainAll(Collection c) { 900 | return s.retainAll(c); 901 | } 902 | 903 | public Object[] toArray() { 904 | return s.toArray(); 905 | } 906 | 907 | public T[] toArray(T[] a) { 908 | return s.toArray(a); 909 | } 910 | 911 | @Override 912 | public boolean equals(Object o) { 913 | return s.equals(o); 914 | } 915 | 916 | @Override 917 | public int hashCode() { 918 | return s.hashCode(); 919 | } 920 | 921 | @Override 922 | public String toString() { 923 | return s.toString(); 924 | } 925 | } 926 | 927 | ``` 928 | 929 | У данного подхода мало недостатков, один из них `SELF problem`, остальные менее существенные. 930 | 931 | Пользоваться наследованием можно, если между классом и суперклассом есть реальная связь типа и подтипа. Но даже в этом 932 | случае применение наследование может сделать программу ненадежной, особенно если подкласс и суперкласс принадлежат к 933 | разным пакетам, а сам суперкласс не был изначально предназначен для расширения. 934 | 935 | ## 4.5 Проектируйте и документируйте наследование, либо запрещайте его (Item 19) 936 | 937 | Класс должен документировать, какие из методов он использует сам (self-use), которые могут быть переопределены. Для 938 | каждого открытого или защищенного метода документация должна указывать: какие методы он вызывает, которые могу быть 939 | переопределены; в какой последовательности; каким образом результаты их вызова влияют на дальнейшую работу. 940 | 941 | Для создания более эффективных подклассов, суперкласс может предоставлять точки входа при внутренней обработке в виде 942 | разумно выбранных защищенных методов. 943 | 944 | Единственный способ протестировать класс, предназначенный для наследования - написать подклассы. 945 | Необходимо протестировать класс путем написания подклассов до того, как он буде выпущен. 946 | 947 | Конструкторы класса не должны вызывать методы, которые могут быть переопределены. Не напрямую и не косвенно. 948 | 949 | Если родительский класс реализовывает интерфейс `Cloneable` или `Serializable`, то ни методу `clone`, ни 950 | методу `readObject` не разрешается вызывать методы, которые могут быть переопределены, ни непосредственно, ни косвенно. 951 | Также эти методы должны иметь модификатор доступа `protected`. 952 | 953 | Если класс не предполагается для наследования, то лучше запретить наследование данного класса: 954 | 955 | - Объявить как `final` 956 | - Сделать все конструкторы закрытыми или доступные в пределах пакета, а вместо них использовать статистические фабрики. 957 | 958 | ## 4.6 Предпочитайте интерфейсы абстрактным классам (Item 20) 959 | 960 | В Java разрешено только единичное наследование, это ограничение на абстрактные классы серьезно сдерживает их 961 | использование в качестве определений типов. 962 | 963 | Существующие классы можно легко приспособить для реализации нового интерфейса. 964 | 965 | Интерфейсы идеально подходят для создания миксинов(класс, который может иметь дополнительное поведение). 966 | 967 | Интерфейс позволяет создавать фреймворк неиерархического типа. 968 | 969 | Интерфейс обеспечивают безопасное и мощное развитие функциональности с использованием 970 | класса-оболочки ([Item 18](#44-предпочитайте-композицию-наследованию-item-18)). 971 | 972 | Можно объединить преимущества интерфейсов и абстрактных классов, предоставляя абстрактный класс скелетной реализации, 973 | сопутствующий интерфейсу. 974 | 975 | ```java 976 | // Concrete implementation built atop skeletal implementation 977 | public abstract class AbstractMapEntry implements Map.Entry { 978 | // Записи в изменяемом отображении должны перекрывать этот метод 979 | @Override 980 | public V setValue(V value) { 981 | throw new UnsupportedOperationException(); 982 | } 983 | 984 | // Реализует общий контракт Map.Entry.equals 985 | @Override 986 | public boolean equals(Object o) { 987 | if (o == this) return true; 988 | if (!(o instanceof Map.Entry)) return false; 989 | Map.Entry e = (Map.Entry) o; 990 | return Objects.equals(e.getKeyO, getKeyO) && Objects.equals(e.getValue(), getValue()); 991 | } 992 | 993 | // Реализует общий контракт Map.Entry.hashCode 994 | @Override 995 | public int hashCode() { 996 | return Objects.hashCode(getKey()) ^ Objects.hashCode(getValue()); 997 | } 998 | 999 | @Override 1000 | public String toString() { 1001 | return getKeyO + +getValue(); 1002 | } 1003 | } 1004 | 1005 | ``` 1006 | 1007 | Необходима документация скелетной реализации. 1008 | 1009 | ## 4.7 Проектируйте интерфейсы для потомков (Item 21) 1010 | 1011 | В Java 8 у интерфейсов появились дефолтные методы. Но добавление новых методов к существующим интерфейсам сопряжено с 1012 | риском. Если добавлять дефолтные методы в существующий интерфейс, то не во всех реализациях интерфейса метод будет 1013 | работать корректно. 1014 | 1015 | Не всегда возможно написать дефолтный метод, который поддерживает все инварианты всех мыслимых реализаций. 1016 | 1017 | В присутствии дефолтных методов существующие реализации интерфейсов могут компилироваться без ошибок или предупреждений, 1018 | но сбоить во время выполнения. 1019 | 1020 | ## 4.8 Используйте интерфейсы только для определения типов (Item 22) 1021 | 1022 | Интерфейс следует использовать в качестве определения типа, который может быть использован для ссылки на экземпляры 1023 | класса. 1024 | 1025 | Не следует использовать интерфейс констант: 1026 | 1027 | ```java 1028 | // Constant interface antipattern. Don't do it ! 1029 | public interface PhysicalConstants { 1030 | static final double AVOGADROS_NUMBER = 6.022_140_857e23; 1031 | static final double BOLTZMAN_CONSTANT = 1.380_648_52e-23; 1032 | } 1033 | 1034 | ``` 1035 | 1036 | Лучше использовать утильный класс констант: 1037 | 1038 | ```java 1039 | public class PhysicalConstants { 1040 | private PhysicalConstants() { 1041 | } // Prevents instantiation 1042 | 1043 | public static final double AVOGRADOS_NUMBER = 6.02214199e23; 1044 | public static final double BOLTZAN_CONSTANT = 1.3806503e-23; 1045 | public static final double ELECTRON_MASS = 9.10938188e-31; 1046 | } 1047 | 1048 | ``` 1049 | 1050 | ## 4.9 Предпочитайте иерархии классов дескрипторам классов (Item 23) 1051 | 1052 | Поле класса _дескриптор_ - указывает разновидность класса, который может находиться в разных состояниях. 1053 | 1054 | Классы с дескрипторами многословны, склонны к ошибкам и неэффективны. 1055 | 1056 | Применение дескрипторов является лишь бледным подобием иерархии классов. И лучше задуматься над рефакторингом. 1057 | 1058 | ## 4.10 Предпочитайте вложенный статический класс вложенному внутреннему классу (Item 24) 1059 | 1060 | Виды классов внутри других классов(nested class) в Java: 1061 | 1062 | - Вложенный статический класс(static member class) 1063 | - Вложенный внутренний класс(nonstatic member class) 1064 | - Анонимный класс(anonymous class) 1065 | - Локальный класс(local class) 1066 | 1067 | Алгоритм подбора вида класса: 1068 | 1069 | * Если класс внутри другого класса должен быть виден за пределами метода или он слишком длинный, для размещения в 1070 | границах метода, используйте вложенный класс(static или nonstatic). 1071 | * Если каждому экземпляру вложенного необходима ссылка на включающий его экземпляр, делайте его внутренним классом( 1072 | nonstatic member class) 1073 | * В остальных случаях статистический(static member class) 1074 | * Если класс находится внутри метода и нужно создавать экземпляр в одном месте программы и имеется тип для этого 1075 | класса - анонимный класс(anonymous class) 1076 | * В остальных случаях локальный(local class) 1077 | 1078 | ## 4.11 Ограничивайтесь одним классом верхнего уровня на исходный файл (Item 25) 1079 | 1080 | Никогда не размещайте несколько классов верхнего уровня или интерфейсов в одни исходном файле. Это гарантирует, что не 1081 | будет нескольких определений одного класса во время компиляции. 1082 | 1083 | # 5 Обобщенное программирование 1084 | 1085 | ## 5.1 Не используйте сырые типы (Item 26) 1086 | 1087 | Основная терминология 1088 | 1089 | | **Термин** | **Пример** | **Item** | 1090 | |-------------------------------------------|------------------------------------|----------------------------------------------------------------------------------------------| 1091 | | Параметризованный тип | `List` | [26](#51-не-используйте-сырые-типы-item-26) | 1092 | | Актуальный параметр типа | `String` | [26](#51-не-используйте-сырые-типы-item-26) | 1093 | | Обобщенный тип | `List` | [26](#51-не-используйте-сырые-типы-item-26), [29](#54-предпочитайте-обобщенные-типы-item-29) | 1094 | | Формальный параметр типа | `E` | [26](#51-не-используйте-сырые-типы-item-26) | 1095 | | Неограниченный тип с символом подстановки | `List` | [26](#51-не-используйте-сырые-типы-item-26) | 1096 | | Сырой тип | `List` | [26](#51-не-используйте-сырые-типы-item-26) | 1097 | | Ограниченный параметр типа | `` | [29](#54-предпочитайте-обобщенные-типы-item-29) | 1098 | | Рекурсивно ограниченный тип | `>` | [30](#55-предпочитайте-обобщенные-методы-item-30) | 1099 | | Ограниченный тип с символом подстановки | `List` | [31](#56-используйте-ограниченные-символы-подстановки-для-повышения-гибкости-api-item-31) | 1100 | | Обобщенный метод | `static List asList(E[] a)` | [30](#55-предпочитайте-обобщенные-методы-item-30) | 1101 | | Токен типа | `String.class` | [33](#58-применяйте-безопасные-с-точки-зрения-типов-гетерогенные-контейнеры-item-33) | 1102 | 1103 | Использование сырых типов может привести к ошибке только во время выполнения, по этому не рекомендуется их использовать. 1104 | Они оставлены для обратной совместимости. 1105 | 1106 | ```java 1107 | private final Collection stamps =...; 1108 | 1109 | stamps. 1110 | 1111 | add(new Coin(...)); //Erroneous insertion. Does not throw any error 1112 | 1113 | Stamp s = (Stamp) stamps.get(i); // Throws ClassCastException when getting the Coin 1114 | ``` 1115 | 1116 | ```java 1117 | private final Collection stamps =...; 1118 | 1119 | stamps. 1120 | 1121 | add(new Coin()); // Compile time error. Coin can not be add to Collection 1122 | 1123 | Stamp s = stamps.get(i); // No need casting 1124 | ``` 1125 | 1126 | Правила образования подтипов для обобщенных типов: `List` является подтипом сырого `List`, но не является 1127 | подтипом `List`. Теряется безопасность типов при использовании сырых типов, но не при использовании 1128 | параметризованных типов типа `List`. 1129 | 1130 | Если фактический параметр типа не известен, то в качестве альтернативы для несформированного типа можно использовать 1131 | неограниченный тип с символом подстановки, например `Collection`. Данный подход безопасней, поскольку в данную 1132 | коллекцию нельзя поместить любой элемент(кроме `null`), а сырой тип можно. 1133 | 1134 | Исключения, когда можно использовать сырые типы: 1135 | 1136 | 1. Необходимо использовать несформированные типы в литералах классов: `List.class`, `String[].class` и `int.class` 1137 | разрешены, а `List.class` и `List.class` - нет. 1138 | 2. Использование оператора `instanceof` с обобщенными типами `if(o instanceof Set){Set = (Set)o;}` 1139 | 1140 | ## 5.2 Устраняйте предупреждение о непроверяемом коде (Item 27) 1141 | 1142 | При использовании обобщенных элементов, можно столкнуться с предупреждением компилятора: 1143 | 1144 | - о непроверяемом приведении 1145 | - о непроверяемом вызове метода 1146 | - о непроверяемом параметризованном типе с переменным количеством аргументов 1147 | - о непроверяемом преобразовании 1148 | 1149 | Следует устранять все предупреждения о непроверяемом коде, какие можно устранить! 1150 | 1151 | Если нельзя устранить предупреждения, но есть уверенность, что код, о котором предупреждает компилятор, безопасен, то 1152 | тогда(и только тогда!) можно скрыть предупреждения с помощью аннотации `@SupressWarnings("unchecked")`. 1153 | 1154 | Всегда используйте аннотацию `@SupressWarnings` в наименьшей возможной области видимости. 1155 | 1156 | Каждый раз при использовании `@SupressWarnings("unchecked")` нужно добавлять комментарий с пояснением, почему в данном 1157 | случае этот код безопасен. 1158 | 1159 | ## 5.3 Предпочитайте списки массивам (Item 28) 1160 | 1161 | Отличия массивов от списков: 1162 | 1163 | 1. Массивы **ковариантны** - если `Sub` является подтипом `Super`, то `Sub[]` является подтипом `Super[]`. Обобщенные 1164 | типы **инвариантны** - для двух различных типов `Type1` и `Type2` тип `List` не является ни подтипом, 1165 | ни супертипом `List`. 1166 | 1167 | 2. Массивы являются типами, доступными во время выполнения программы. Обобщенные типы, реализуются с использованием 1168 | затирания, то есть тип доступен во время компиляции, но во время исполнения неизвестен. 1169 | 1170 | С учетом данных отличий, массивы обеспечивают безопасность времени выполнения с точки зрения типов, но не безопасность 1171 | типов при компиляции, и наоборот. 1172 | 1173 | ```java 1174 | class Item27 { 1175 | void method27() { 1176 | // Fails at runtime 1177 | Object[] objectArray = new Long[1]; 1178 | objectArray[0] = "I don't fit in"; // Throws ArrayStoreException 1179 | 1180 | // Won't compile 1181 | List ol = new ArrayList(); // Incompatible types 1182 | ol.add("I don't fit in"); 1183 | } 1184 | } 1185 | 1186 | ``` 1187 | 1188 | Из-за этих отличий нельзя перемешивать листы и массивы. Нельзя создавать подобные 1189 | элементы: `List[]`, `new List[]`, `new E[]`. 1190 | 1191 | ## 5.4 Предпочитайте обобщенные типы (Item 29) 1192 | 1193 | Обобщенные типы безопаснее и проще в использовании, чем типы, которые требует приведений в клиентском коде. При 1194 | разработке новых типов убедитесь, что они могут быть использованы без таких приведений. 1195 | 1196 | ## 5.5 Предпочитайте обобщенные методы (Item 30) 1197 | 1198 | Обобщенные методы, подобно обобщенным типам, безопаснее и проще в использовании, чем методы требующие от своих клиентов 1199 | использовать явные приведения входных параметров и возвращаемых значений. 1200 | 1201 | Для метода сравнения объектов в коллекции, каждый объект должен быть сопоставим с любым другим её элементом. Этом можно 1202 | сделать с помощью рекурсивного ограничения `>`. 1203 | 1204 | ## 5.6 Используйте ограниченные символы подстановки для повышения гибкости API (Item 31) 1205 | 1206 | Поскольку параметризованные типы являются инвариантными, то невозможно в коллекцию `List` вставить 1207 | объект `Integer` используя только формальный параметр типа `E`. 1208 | 1209 | ```java 1210 | class Item31 { 1211 | public void pushAll(Iterable src) { 1212 | for (E e : src) 1213 | puhs(e); 1214 | } 1215 | 1216 | public static void main(String[] args) { 1217 | // Integer is a subtype of Number 1218 | Stack numberStack = new Stack(); 1219 | Iterable integers =...; 1220 | numberStack.pushAll(integers); //Error message here: List is not a subtype of List 1221 | } 1222 | } 1223 | ``` 1224 | 1225 | Что бы это было возможно, можно использовать ограниченный символ подстановки 1226 | 1227 | Producer: 1228 | 1229 | ```java 1230 | class Item31 { 1231 | // We want to push in everything that is E or inherits E 1232 | public void pushAll(Iterable src) { 1233 | for (E e : src) { 1234 | push(e); 1235 | } 1236 | } 1237 | } 1238 | 1239 | ``` 1240 | 1241 | В данном случае параметризованный тип является производителем(producer). Также параметризованный тип может быть 1242 | потребителем(consumer), например получить объекты. 1243 | 1244 | Consumer: 1245 | 1246 | ```java 1247 | class Item31 { 1248 | public void popAll(Collection dst) { 1249 | while (!isEmpty()) dst.add(pop()); 1250 | } 1251 | } 1252 | 1253 | ``` 1254 | 1255 | Правило **PECS** - producer-extends, consumer-super 1256 | 1257 | Не используйте ограниченные типы с символами подстановки в качестве возвращаемых типов. Пользователь данного элемента не 1258 | должен думать о типах символов подстановки. 1259 | 1260 | `Comparable` и `Comparators` всегда потребители. Используйте `Comparable` и `Comparator`. 1261 | 1262 | Если параметр типа появляется в объявлении метода только один раз, его следует заменить символом подстановки: 1263 | 1264 | ```java 1265 | public static void swap(List list, int i, int j) 1266 | 1267 | public static void swap(List list, int i, int j) //Более предпочтительный способ 1268 | ``` 1269 | 1270 | ## 5.7 Аккуратно сочетайте обобщенные типы и переменное количество аргументов (Item 32) 1271 | 1272 | Следует помнить, что методы с переменным количеством аргументов и обобщенные типы взаимодействуют не очень хорошо, 1273 | потому что параметр переменной длины представляет собой утечку абстракции, построенную поверх массива, а правила типов 1274 | для массивов отличаются от правил для обобщенных типов. 1275 | 1276 | Если метод вызывается с переменным числом параметров, выведение типов которых недоступно во время выполнения, компилятор 1277 | выдает предупреждение для такого вызова. 1278 | 1279 | Опасно хранить значение в обобщенном массиве-параметре переменной длины. 1280 | 1281 | ```java 1282 | public class Dangerous { 1283 | // Mixing generics and varargs can violate type safety! 1284 | static void dangerous(List... stringLists) { 1285 | List intList = List.of(42); 1286 | Object[] objects = stringLists; 1287 | objects[0] = intList; // Heap pollution 1288 | String s = stringLists[0].get(0); // ClassCastException 1289 | } 1290 | } 1291 | 1292 | ``` 1293 | 1294 | Но методы с переменным количеством параметров обобщенных или параметризованных типов могут быть весьма полезны на 1295 | практике, поэтому разработчики языка решили оставить эту несогласованность `Arrays.asList(T... a)`, `Collections.addAll( 1296 | Collection)` и `EnumSet.of(E first, E... rest)`. 1297 | 1298 | Обобщенный метод с переменным количеством аргументов безопасен, если: 1299 | 1300 | 1. Он ничего не сохраняет в массиве параметра переменной длины; 1301 | 2. Он не делает массив (или его клон) видимым ненадежному коду. 1302 | 1303 | Аннотация `@SafeVarargs` представляет собой обещание безопасности с точки зрения типов от автора метода. Используйте 1304 | `@SafeVarargs` с каждым методом с параметром переменной длины обобщенного или параметризованного типа, если гарантируете 1305 | безопасность. 1306 | 1307 | ## 5.8 Применяйте безопасные с точки зрения типов гетерогенные контейнеры (Item 33) 1308 | 1309 | Использование обобщенных типов ограничивает фиксированным количествам параметров типа в контейнере. Например, 1310 | коллекции `Set` нельзя сделать произвольное количество параметров типа `Set`. 1311 | 1312 | Можно это обойти, помещая параметр типа в ключ, а не в контейнер. 1313 | 1314 | ```java 1315 | 1316 | public class Favorites { 1317 | private Map, Object> favorites = new HashMap, Object>(); 1318 | 1319 | public void putFavorites(Class type, T instance) { 1320 | if (type == null) 1321 | throw new NullPointerException("Type is null"); 1322 | favorites.put(type, type.cast(instance));//runtime safety with a dynamic cast 1323 | } 1324 | 1325 | public getFavorite(Class type) { 1326 | return type.cast(favorites.get(type)); 1327 | } 1328 | } 1329 | 1330 | ``` 1331 | 1332 | # 6 Перечисления и аннотации 1333 | 1334 | ## 6.1 Используйте перечисление вместо констант int (Item 34) 1335 | 1336 | Тип перечисления(enum) - это классы, которые экспортируют по одному экземпляру для каждой константы перечисления, 1337 | используя открытое статическое финальное поле. 1338 | 1339 | До того как типы перечисление появились в языке Java, был распространен шаблон перечисления int(int enum pattern). 1340 | Данный способ не обеспечивает ни безопасности с точки зрения типов, ни выразительности. Данный шаблон больше не 1341 | рекомендуется. Так же как и шаблон перечисления String(String pattern enum), который к тому же не производительный. 1342 | 1343 | Чтобы связать данные с константами причисления, следует объявить поля экземпляров и написать конструктор, который 1344 | получает данные и сохраняет их в поле. Перечисления неизменяемые, все поля должны 1345 | быть `final`([Item 17](#43-минимизируйте-изменяемость-item-17)). Также желательно их сделать `private`. 1346 | 1347 | ```java 1348 | public enum Planet { 1349 | MERCURY(3.302e+23, 2.439e6), 1350 | VENUS(4.869e+24, 6.052e6); 1351 | // ... 1352 | 1353 | private final double mass; // In kilograms 1354 | private final double radius; // In meters 1355 | private final double surfaceGravity; // In m / s^2 1356 | 1357 | // Universal gravitational constant in m^3 / kg s^2 1358 | private static final double G = 6.67300E-11; 1359 | 1360 | // Constructor 1361 | Planet(double mass, double radius) { 1362 | this.mass = mass; 1363 | this.radius = radius; 1364 | surfaceGravity = G * mass / (radius * radius); 1365 | } 1366 | 1367 | public double mass() { 1368 | return mass; 1369 | } 1370 | 1371 | public double radius() { 1372 | return radius; 1373 | } 1374 | 1375 | public double surfaceGravity() { 1376 | return surfaceGravity; 1377 | } 1378 | 1379 | public double surfaceWeight(double mass) { 1380 | return mass * surfaceGravity; // F = ma 1381 | } 1382 | } 1383 | 1384 | ``` 1385 | 1386 | Если применение `enum` связано с определенными классами верхнего уроня, оно должно быть классом-членом класса верхнего 1387 | уровня ([Item 24](#410-предпочитайте-вложенный-статический-класс-вложенному-внутреннему-классу-item-24)). 1388 | 1389 | Можно связать поведение с константами. Для этого нужно объявить абстрактный метод в типе перечисления и перекрыть его 1390 | конкретным методом для каждой константы в теле класса, зависимого от константы(constant-specific class body). Такие 1391 | методы известны как реализации методов, зависимых от констант(constant-specific method implementations). 1392 | 1393 | ```java 1394 | public enum Operation { 1395 | PLUS("+") { 1396 | public double apply(double x, double y) { 1397 | return x + y; 1398 | } 1399 | }, 1400 | MINUS("-") { 1401 | public double apply(double x, double y) { 1402 | return x - y; 1403 | } 1404 | }, 1405 | TIMES("*") { 1406 | public double apply(double x, double y) { 1407 | return x * y; 1408 | } 1409 | }, 1410 | DIVIDE("/") { 1411 | public double apply(double x, double y) { 1412 | return x / y; 1413 | } 1414 | }; 1415 | 1416 | private final String symbol; 1417 | 1418 | Operation(String symbol) { 1419 | this.symbol = symbol; 1420 | } 1421 | 1422 | @Override 1423 | public String toString() { 1424 | return symbol; 1425 | } 1426 | 1427 | public abstract double apply(double x, double y); 1428 | 1429 | // Implementing a fromString method on an enum type (Page 164) 1430 | private static final Map stringToEnum = 1431 | Stream.of(values()).collect( 1432 | toMap(Object::toString, e -> e)); 1433 | 1434 | // Returns Operation for string, if any 1435 | public static Optional fromString(String symbol) { 1436 | return Optional.ofNullable(stringToEnum.get(symbol)); 1437 | } 1438 | 1439 | public static void main(String[] args) { 1440 | double x = Double.parseDouble(args[0]); 1441 | double y = Double.parseDouble(args[1]); 1442 | for (Operation op : Operation.values()) 1443 | System.out.printf("%f %s %f = %f%n", 1444 | x, op, y, op.apply(x, y)); 1445 | } 1446 | } 1447 | ``` 1448 | 1449 | Применения `switch` к `enum` опасно с точки зрения поддержки и менее гибко. Альтернатива - использования 1450 | перечисления-стратегии. 1451 | 1452 | ```java 1453 | enum PayrollDay { 1454 | MONDAY, 1455 | TUESDAY, 1456 | WEDNESDAY, 1457 | THURSDAY, 1458 | FRIDAY, 1459 | SATURDAY(WEEKEND), 1460 | SUNDAY(WEEKEND); 1461 | 1462 | private final PayType payType; 1463 | 1464 | PayrollDay() { 1465 | this(WEEKDAY); // default 1466 | } 1467 | 1468 | PayrollDay(PayType payType) { 1469 | this.payType = payType; 1470 | } 1471 | 1472 | int pay(int minutesWorked, int payRate) { 1473 | return payType.pay(minutesWorked, payRate); 1474 | } 1475 | 1476 | // The strategy enum type 1477 | enum PayType { 1478 | WEEKDAY { 1479 | int overtimePay(int minsWorked, int payRate) { 1480 | return minsWorked <= MINS_PER_SHIFT ? 0 : (minsWorked - MINS_PER_SHIFT) * payRate / 2; 1481 | } 1482 | }, 1483 | WEEKEND { 1484 | int overtimePay(int minsWorked, int payRate) { 1485 | return minsWorked * payRate / 2; 1486 | } 1487 | }; 1488 | 1489 | abstract int overtimePay(int mins, int payRate); 1490 | 1491 | private static final int MINS_PER_SHIFT = 8 * 60; 1492 | 1493 | int pay(int minsWorked, int payRate) { 1494 | int basePay = minsWorked * payRate; 1495 | return basePay + overtimePay(minsWorked, payRate); 1496 | } 1497 | } 1498 | 1499 | public static void main(String[] args) { 1500 | for (PayrollDay day : values()) System.out.printf("%-10s%d%n", day, day.pay(8 * 60, 1)); 1501 | } 1502 | } 1503 | 1504 | ``` 1505 | 1506 | Конструкция `switch` с перечислениями хорошо подходит для дополнения типов перечислений поведением, зависимым от 1507 | констант. 1508 | 1509 | ```java 1510 | public class Inverse { 1511 | public static Operation inverse(Operation op) { 1512 | switch (op) { 1513 | case PLUS: 1514 | return Operation.MINUS; 1515 | case MINUS: 1516 | return Operation.PLUS; 1517 | case TIMES: 1518 | return Operation.DIVIDE; 1519 | case DIVIDE: 1520 | return Operation.TIMES; 1521 | 1522 | default: 1523 | throw new AssertionError("Unknown op: " + op); 1524 | } 1525 | } 1526 | } 1527 | 1528 | ``` 1529 | 1530 | Используйте перечисления всегда, когда требуется набор констант, члены которых известны во время компиляции. 1531 | 1532 | ## 6.2 Используйте поля экземпляра вместо порядковых значений (Item 35) 1533 | 1534 | Никогда не выводите значение, связанное с перечислением, из его порядкового номера; вместо этого храните его в поле 1535 | экземпляра. 1536 | 1537 | Лучше так не делать: 1538 | 1539 | ```java 1540 | public enum Ensemble { 1541 | SOLO, 1542 | DUET, 1543 | TRIO; 1544 | 1545 | public int numberOfMusicians() { 1546 | return ordinal() + 1; 1547 | } 1548 | } 1549 | 1550 | ``` 1551 | 1552 | Так можно: 1553 | 1554 | ```java 1555 | public enum Ensemble { 1556 | SOLO(1), DUET(2), TRIO(3); 1557 | 1558 | private final int numberOfMusicians; 1559 | 1560 | Ensemble(int size) { 1561 | this.numberOfMusicians = size; 1562 | } 1563 | 1564 | public int numberOfMusicians() { 1565 | return numberOfMusicians; 1566 | } 1567 | } 1568 | 1569 | ``` 1570 | 1571 | ## 6.3 Используйте EnumSet вместо битовых полей (Item 36) 1572 | 1573 | До появления `enum`, если элементы перечисляемого типа использовались в коллекциях, то применялась схема битовых полей. 1574 | 1575 | Лучшая альтернатива использовать коллекцию `EnumSet`, которая объединяет в себе кратность и производительность битовых 1576 | полей, со всем преимуществами `enum`. 1577 | 1578 | ## 6.4 Используйте EnumMap вместо индексирования порядковыми номерами (Item 37) 1579 | 1580 | Используйте `EnumMap`, если требуется сгруппировать объекты по элементам перечисления. 1581 | 1582 | ```java 1583 | class Plant { 1584 | Map> plantsByLifeCycle = 1585 | Arrays.stream(garden) 1586 | .collect(groupingBy(p -> p.lifeCycle, () -> new EnumMap<>(LifeCycle.class), toSet())); 1587 | } 1588 | 1589 | ``` 1590 | 1591 | Если представление является многомерным, используйте `EnumMap<...,EnumMap<...>>`. 1592 | 1593 | ## 6.5 Имитируйте расширяемые перечисления с помощью интерфейсов (Item 38) 1594 | 1595 | Классы `enum` невозможно сделать расширяемым, потому что каждый класс неявно наследуется от класса `Enum`. Но можно 1596 | имитировать расширение с помощью реализации интерфейса. 1597 | 1598 | ```java 1599 | public interface Operation { 1600 | double apply(double x, double y); 1601 | } 1602 | 1603 | public enum BasicOperation implements Operation { 1604 | PLUS("+") { 1605 | public double apply(double x, double y) { 1606 | return x + y; 1607 | } 1608 | }; 1609 | 1610 | private final String symbol; 1611 | 1612 | BasicOperation(String symbol) { 1613 | this.symbol = symbol; 1614 | } 1615 | } 1616 | 1617 | public enum ExtendedOperation implements Operation { 1618 | EXP("^") { 1619 | public double apply(double x, double y) { 1620 | return Math.pow(x, y); 1621 | } 1622 | }; 1623 | 1624 | private final String symbol; 1625 | 1626 | ExtendedOperation(String symbol) { 1627 | this.symbol = symbol; 1628 | } 1629 | } 1630 | 1631 | ``` 1632 | 1633 | Экземпляры этих типов могут использовать везде, где могут использовать экземпляры базового типа перечисления. 1634 | 1635 | ## 6.6 Предпочитайте аннотации схемам именования (Item 39) 1636 | 1637 | Для указания элементов, которые требуют специальной обработки исторически используется схема именования. 1638 | Например, до 4 версии `JUnit` требовалось начинать названия метода со слова `test...`. 1639 | 1640 | Минусы данного подхода: 1641 | 1642 | - Возможны опечатки наименования, при этом обработчик не будет жаловаться. Например, `tset`. 1643 | - Нет понятного способа определения, что схемы именования используются только в соответствующих элементах программы. 1644 | Например, назвать класс начиная со слова `Test...`, в надежде, что методы тестов сработают, но это не так. 1645 | - Нет хорошего способа связать значения параметров с элементами программы. Например, нужно указать, чтобы тест 1646 | обрабатывал конкретное исключение. 1647 | 1648 | Использование аннотаций позволяет помечать элементы, которые требуют специальной обработки и не обладают теми же 1649 | недостатками схемы именования. Начиная с версии 4 `JUnit` используется аннотация `@Test` (аннотация-маркер) для методов 1650 | тестов. 1651 | 1652 | Обязательные параметры аннотации: 1653 | 1654 | Тип хранения (`@Retention(RetentionPolicy.RUNTIME)`) 1655 | 1656 | | **Название** | **Описание** | 1657 | |--------------|----------------------------------------------------------------------| 1658 | | SOURCE | Используется только при написании класса, отбрасывается компилятором | 1659 | | CLASS | Обрабатывается во время компиляции, но игнорируется JVM | 1660 | | RUNTIME | Обрабатывается во время компиляции и JVM | 1661 | 1662 | Тип возможного использования (`@Target(ElementType.METHOD)`) 1663 | 1664 | | **Название** | **Описание** | 1665 | |-----------------|-------------------------| 1666 | | ANNOTATION_TYPE | Другая аннотация | 1667 | | CONSTRUCTOR | Конструктор класса | 1668 | | FIELD | Поле класса | 1669 | | LOCAL_VARIABLE | Локальная переменная | 1670 | | METHOD | Метод класса | 1671 | | PACKAGE | Описание пакета | 1672 | | PARAMETER | Параметр метода | 1673 | | TYPE | Указывается над классом | 1674 | 1675 | Аннотация, использование, обработка: 1676 | 1677 | ```java 1678 | //Annotation with array parameter 1679 | @Retention(RetentionPolicy.RUNTIME) 1680 | @Target(ElementType.METHOD) 1681 | public @interface ExceptionTest { 1682 | Class[] value(); 1683 | } 1684 | 1685 | //Usage of the annotation 1686 | @ExceptionTest({IndexOutOfBoundsException.class, NullPointerException.class}) 1687 | public void myMethod() { 1688 | } 1689 | 1690 | //By reflexion you can use the annotation this way 1691 | m. 1692 | 1693 | isAnnotationPresent(ExceptionTest .class); 1694 | 1695 | //Or get the values this way : 1696 | Class[] excTypes = m.getAnnotation(ExceptionTest.class).value(); 1697 | 1698 | ``` 1699 | 1700 | Плюсы аннотации: 1701 | 1702 | - При неправильном именовании, может не пройти компилятор (`@Retention(RetentionPolicy.RUNTIME)`) 1703 | - Можно ограничить использование элемента (`@Target(ElementType.METHOD)`) 1704 | - Можно указать параметр аннотации в качестве примитивного типа, объекта или 1705 | массива (`@ExceptionTest({IndexOutOfBoundsException.class, NullPointerException.class}`) 1706 | - Можно использовать повторяемый тип аннотации: 1707 | 1708 | ```java 1709 | 1710 | @ExceptionTest(IndexOutOfBoundsException.class) 1711 | @ExceptionTest(NullPointerException.class) 1712 | public static void doublyBad() { 1713 | } 1714 | ``` 1715 | 1716 | ## 6.7 Последовательно используйте аннотацию Override (Item 40) 1717 | 1718 | Следует использовать аннотацию `@Override` для каждого объявления метода, которое, как вы полагаете, перекрывает 1719 | объявление суперкласса. 1720 | 1721 | Если объявить её у метода, который неправильно переопределяет метод суперкласса, то компилятор вызовет ошибку. 1722 | 1723 | ```java 1724 | 1725 | @Override //Compilation error 1726 | public boolean equals(Bigram b) { 1727 | return b.first == first && b.second == second; 1728 | } 1729 | 1730 | @Override // The correct sign to override the super method 1731 | public boolean equals(Object b) { 1732 | } 1733 | 1734 | ``` 1735 | 1736 | ## 6.8 Используйте интерфейсы-маркеры для определения типов (Item 41) 1737 | 1738 | Интерфейс-маркер представляет собой интерфейс, не содержащий методов для реализации, а лишь маркирует класс, реализующий 1739 | интерфейс, как имеющий определенные свойства. Например, `Serializable`. 1740 | 1741 | Преимущества интерфейс-маркеров над 1742 | аннотациями-маркерами ([Item 30](#66-предпочитайте-аннотации-схемам-именования-item-39)): 1743 | 1744 | - Интерфейсы-маркеры определяют тип, который реализуется экземплярами маркированного класса; аннотации-маркеры этим 1745 | свойством не обладают 1746 | - Интерфейсы-маркеры могут быть более точно нацелены 1747 | 1748 | Преимущество аннотации-маркерами перед интерфейсами-маркерами: они являются частью более мощной системы аннотаций. 1749 | 1750 | Необходимо использовать аннотации, если маркер применяется к любому элементу программы, кроме класса или интерфейса. 1751 | Если маркер применяется только к классам или интерфейсам, то нужно задать себе вопрос: "Может ограничиться одним или 1752 | несколькими методами, которые принимают только объекты, типы которых это интерфейсы-маркеры?". Если это так, то 1753 | предпочесть интерфейс-маркер. 1754 | 1755 | Если вы пишете тип аннотации-маркера, целью которого является `ElementType.TYPE`, то потратьте время на выяснение, 1756 | действительно ли это должен быть тип аннотации или же более целесообразным будет применение интерфейса-маркера. 1757 | 1758 | # 7 Лямбда-выражения и Stream API 1759 | 1760 | ## 7.1 Предпочитайте лямбда-выражения анонимным классам (Item 42) 1761 | 1762 | Функциональный интерфейс (до Java 8 "функциональный тип") - это интерфейс, который имеет один метод для реализации. При 1763 | этом нет ограничения на другие методы. 1764 | Функциональный объект - это экземпляр класса, реализующий функциональный интерфейс. 1765 | 1766 | До Java 8 основным способом создания функционального объекта был анонимный 1767 | класс ([Item 24](#410-предпочитайте-вложенный-статический-класс-вложенному-внутреннему-классу-item-24)). В Java 8 язык 1768 | формализовал концепцию интерфейсов с единственным абстрактным методом как отдельную, заслуживающую особой обработки. И 1769 | язык позволяет создавать их экземпляры с помощью лямбда-выражений. Лямбда-выражения функционально равны анонимным 1770 | классам. 1771 | 1772 | ```java 1773 | public class SortFourWays { 1774 | public static void main(String[] args) { 1775 | 1776 | // Anonymous class instance as a function object - obsolete! 1777 | Collections.sort( 1778 | words, 1779 | new Comparator<>() { 1780 | @Override 1781 | public int compare(String s1, String s2) { 1782 | return Integer.compare(s1.length(), s2.length()); 1783 | } 1784 | }); 1785 | 1786 | // Lambda expression as function object (replaces anonymous class) 1787 | Collections.sort( 1788 | words, 1789 | Comparator < String > comparator = (s1, s2) -> Integer.compare(s1.length(), s2.length())); 1790 | } 1791 | } 1792 | 1793 | ``` 1794 | 1795 | Типы для лямбда-выражения самого объекта и его параметров можно не записывать, компилятор определяет типы из контекста. 1796 | Опустите типы всех параметров лямбда-выражения, если только они не делают вашу программу яснее. 1797 | 1798 | Реализацию компаратора можно и упростить, использовав метод построения компаратора + ссылку на 1799 | метод ([Item 43](#73-предпочитайте-использовать-стандартные-функциональные-интерфейсы-item-44)): 1800 | `Collections.sort(words, comparingInt(String::length)))`. 1801 | 1802 | Добавление лямбда-выражения в язык делает практичным использование функциональных объектов. 1803 | 1804 | В примере из [Item 34](#61-используйте-перечисление-вместо-констант-int-item-34) поведение связанное с константами, 1805 | можно реализовать с помощью лямбда-выражения. Использовав стандартный функциональный интерфейс `DoubleBinaryOperator`( 1806 | [Item 44](#73-предпочитайте-использовать-стандартные-функциональные-интерфейсы-item-44)) и реализовать метод `apply`, 1807 | который делегирует вызов метода функционального объекта `applyAsDouble`: 1808 | 1809 | ```java 1810 | public enum Operation { 1811 | PLUS("+", (x, y) -> x + y), 1812 | MINUS("-", (x, y) -> x - y), 1813 | TIMES("*", (x, y) -> x * y), 1814 | DIVIDE("/", (x, y) -> x / y); 1815 | 1816 | private final String symbol; 1817 | private final DoubleBinaryOperator op; 1818 | 1819 | Operation(String symbol, DoubleBinaryOperator op) { 1820 | this.symbol = symbol; 1821 | this.op = op; 1822 | } 1823 | 1824 | @Override 1825 | public String toString() { 1826 | return symbol; 1827 | } 1828 | 1829 | public double apply(double x, double y) { 1830 | return op.applyAsDouble(x, y); 1831 | } 1832 | 1833 | public static void main(String[] args) { 1834 | double x = Double.parseDouble(args[0]); 1835 | double y = Double.parseDouble(args[1]); 1836 | for (Operation op : Operation.values()) 1837 | System.out.printf("%f %s %f = %f%n", x, op, y, op.apply(x, y)); 1838 | } 1839 | } 1840 | 1841 | ``` 1842 | 1843 | Если вычисление требует оставлять пояснений в виде комментариев или превышает несколько строк, не используйте для 1844 | него лямбда-выражение. 1845 | 1846 | Для лямбда-выражения идеально одна строка кода, а три это разумный максимум. Если лямбда-выражения длинное или сложное 1847 | для чтения, либо упростите его, либо сделайте рефакторинг и замените его. 1848 | 1849 | Что можно сделать с анонимными классами, и нельзя - с лямбда-выражениями. 1850 | 1851 | - Если нужно создать экземпляр абстрактного класса, то это можно сделать с помощью анонимного класса, но не 1852 | лямбда-выражения. 1853 | - Анонимные классы можно использовать для создания экземпляров интерфейсов с несколькими абстрактными методами. 1854 | - Лямбда-выражения не может получить ссылку на себя. В лямбда-выражение ключевое слово `this` ссылается на объект в 1855 | котором содержится лямбда выражение. В анонимном классе ключевое слово `this` ссылается на экземпляр анонимного 1856 | класса. 1857 | 1858 | Лямбда-выражения, как и экземпляр анонимного класса нельзя надежно сериализовать и десериализовать. Как можно реже(если 1859 | вообще придется это делать) сериализуйте лямбда-выражение. 1860 | 1861 | ## 7.2 Предпочитайте ссылки на методы лямбда-выражениям (Item 43) 1862 | 1863 | Помимо лямбда-выражения, есть более краткий способ создать функциональный интерфейс - ссылки на метод. Если у метода, на 1864 | который необходимо указать ссылку, параметры совпадают с параметрами лямбда-выражения, то можно использовать специальную 1865 | запись в виде ссылки на метод: 1866 | 1867 | ```java 1868 | public class Freq { 1869 | public static void main(String[] args) { 1870 | 1871 | // Lambda 1872 | frequencyTable.merge(s, 1, (count, incr) -> count + incr); 1873 | 1874 | // Lambda with Integer static method 1875 | frequencyTable.merge(s, 1, (count, incr) -> Integer.sum(count, incr)); 1876 | 1877 | // Method reference 1878 | frequencyTable.merge(s, 1, Integer::sum); 1879 | } 1880 | } 1881 | 1882 | ``` 1883 | 1884 | Чаще ссылка на метод имеет более краткую запись, чем лямбда-выражения. Но если это не так, то лучше использовать 1885 | лямбда-выражения: 1886 | 1887 | `service.execute(GoshThisClassNameIsHumongous::action);` 1888 | 1889 | `service.execute(() -> action());` 1890 | 1891 | Типы ссылок на методы 1892 | 1893 | | **Тип ссылки** | **Пример** | **Эквивалетное лямбда-выражение** | 1894 | |---------------------|--------------------------|-----------------------------------------------------------| 1895 | | Статическая | `Integer::parseInt` | `str -> Integer.parseInt(str)` | 1896 | | Ограниченная | `Instant.now()::isAfter` | `Instant then = Instant.now();`
` t->then.isAfter(t)` | 1897 | | Неограниченная | `String::toLowerCase` | `str -> str.toLowerCase()` | 1898 | | Конструктор класса | `TreeMap::new` | `() -> new TreeMap` | 1899 | | Конструктор массива | `int[]::new` | `len -> new int[len] ` | 1900 | 1901 | ## 7.3 Предпочитайте использовать стандартные функциональные интерфейсы (Item 44) 1902 | 1903 | С появлением лямбда-выражения, значительно изменились лучше практики написания API. Например, паттерн 1904 | **Шаблонный метод**, в котором подкласс переопределяет абстрактный метод для специализации поведения суперкласса, 1905 | становиться менее привлекательным. Современная альтернатива - представить статическую фабрику или конструктор, который 1906 | принимает функциональный объект, с помощью чего будет достигнуть тот же результат. 1907 | 1908 | Поскольку необходимость создания функционального объекта возрастает. Выбор правильного типа функционального параметра 1909 | требует внимания. 1910 | 1911 | Пакет `java.util.function` предоставляет вам большую коллекцию стандартных функциональных интерфейсов. Если один из 1912 | стандартных функциональных интерфейсов выполняет нужную вам работу, в общем случае следует использовать именно его, а не 1913 | специализированный самописный функциональный интерфейс. 1914 | 1915 | Основные функциональные интерфейсы: 1916 | 1917 | | **Интерфейс** | **Сигнатура функции** | **Пример** | 1918 | |---------------------|-----------------------|-----------------------| 1919 | | `UnaryOperator` | `T apply(T t)` | `String::toLowerCase` | 1920 | | `BinaryOperator` | `T apply(T t1, T t2)` | `BigInteger::add` | 1921 | | `Predicate` | `boolean test(T t)` | `Collection::isEmpty` | 1922 | | `Function` | `R apply(T t)` | `Arrays::asList` | 1923 | | `Supplier` | `T get()` | `Instant::now` | 1924 | | `Consumer` | `void accept(T t)` | `System.out::println` | 1925 | 1926 | Если необходимо написать собственный функциональный интерфейс, то к разработке необходимо отнестись с 1927 | осторожностью ([Item 21](#47-проектируйте-интерфейсы-для-потомков-item-21)). Всегда аннотируйте ваши функциональные 1928 | интерфейсы с помощью аннотации `@FunctionalInterface`. 1929 | 1930 | Не делайте перезагрузку метода ([Item 52](#84-перезагружайте-методы-разумно-item-52)), которые принимают разные 1931 | функциональные интерфейсы в одно и той же позиции аргумента, если это может привести к возможной неоднозначности в 1932 | команде клиента. Например, в `ExecutorService` метод `submit` может принимать `Callable` или `Runnable`. 1933 | 1934 | ## 7.4 Разумно используйте Stream API (Item 45) 1935 | 1936 | Stream API - это инструмент, который позволяет работать со структурами данных в стиле функциональных языков 1937 | программирования. 1938 | 1939 | Stream API имеет два вида методов: 1940 | 1941 | 1. Конвейерные — возвращают другой stream, то есть работают как `builder` 1942 | 2. Терминальные — возвращают другой объект, такой как коллекция, примитивы, объекты, `Optional` и т.д. 1943 | 1944 | Stream API вычисляются отложено: вычисления не начинаются до тех пор, пока не будет вызвана завершающая операция, и 1945 | никогда не вычисляются элементы данных, которые не нужны для выполнения завещающей операции. 1946 | 1947 | Stream API может выполнять операции как последовательно, так и параллельно, для этого нужно вызвать метод `parallel` для 1948 | любого потока в конвейере, но это уместным бывает редко. 1949 | 1950 | Злоупотребление Stream API затрудняет чтение и поддержку кода. 1951 | 1952 | Без Stream API: 1953 | 1954 | ```java 1955 | // Prints all large anagram groups in a dictionary iteratively 1956 | public class IterativeAnagrams { 1957 | public static void main(String[] args) throws IOException { 1958 | File dictionary = new File(args[0]); 1959 | int minGroupSize = Integer.parseInt(args[1]); 1960 | 1961 | Map> groups = new HashMap<>(); 1962 | try (Scanner s = new Scanner(dictionary)) { 1963 | while (s.hasNext()) { 1964 | String word = s.next(); 1965 | groups.computeIfAbsent(alphabetize(word), 1966 | (unused) -> new TreeSet<>()).add(word); 1967 | } 1968 | } 1969 | 1970 | for (Set group : groups.values()) 1971 | if (group.size() >= minGroupSize) 1972 | System.out.println(group.size() + ": " + group); 1973 | } 1974 | 1975 | private static String alphabetize(String s) { 1976 | char[] a = s.toCharArray(); 1977 | Arrays.sort(a); 1978 | return new String(a); 1979 | } 1980 | } 1981 | 1982 | ``` 1983 | 1984 | Злоупотребление Stream API: 1985 | 1986 | ```java 1987 | // Overuse of streams - don't do this! 1988 | public class StreamAnagrams { 1989 | public static void main(String[] args) throws IOException { 1990 | Path dictionary = Paths.get(args[0]); 1991 | int minGroupSize = Integer.parseInt(args[1]); 1992 | 1993 | try (Stream words = Files.lines(dictionary)) { 1994 | words.collect( 1995 | groupingBy(word -> word.chars().sorted() 1996 | .collect(StringBuilder::new, 1997 | (sb, c) -> sb.append((char) c), 1998 | StringBuilder::append).toString())) 1999 | .values().stream() 2000 | .filter(group -> group.size() >= minGroupSize) 2001 | .map(group -> group.size() + ": " + group) 2002 | .forEach(System.out::println); 2003 | } 2004 | } 2005 | } 2006 | ``` 2007 | 2008 | Корректное применение Stream API: 2009 | 2010 | ```java 2011 | // Tasteful use of streams enhances clarity and conciseness 2012 | public class HybridAnagrams { 2013 | public static void main(String[] args) throws IOException { 2014 | Path dictionary = Paths.get(args[0]); 2015 | int minGroupSize = Integer.parseInt(args[1]); 2016 | 2017 | try (Stream words = Files.lines(dictionary)) { 2018 | words.collect(groupingBy(word -> alphabetize(word))) 2019 | .values().stream() 2020 | .filter(group -> group.size() >= minGroupSize) 2021 | .forEach(g -> System.out.println(g.size() + ": " + g)); 2022 | } 2023 | } 2024 | 2025 | private static String alphabetize(String s) { 2026 | char[] a = s.toCharArray(); 2027 | Arrays.sort(a); 2028 | return new String(a); 2029 | } 2030 | } 2031 | ``` 2032 | 2033 | В отсутствии явного указания типов тщательное именование параметров лямбда-выражения имеет важное значение для 2034 | удобочитаемости конвейеров потоков. В примере `HybridAnagrams` параметр `g`, должен назваться `group`, но 2035 | тогда получилось длинная строка. 2036 | 2037 | Применение вспомогательных методов для удобочитаемости в конвейерах потоков даже более важно, чем в итеративном коде. В 2038 | примере `HybridAnagrams` используется метод `alphabetize`. 2039 | 2040 | Воздерживайтесь от использования потоков для обработки значений типа char: 2041 | 2042 | `"Hello world!".chars().forEach(System.out::print); -> 721011081081113211911111410810033` 2043 | 2044 | `"Hello world!".chars().forEach(x -> System.out.print((char) x)); -> Hello world!` 2045 | 2046 | Выполняйте рефакторинг существующего кода для использования потоков и используйте их в новом коде только там, где это 2047 | имеет смысла. 2048 | 2049 | Конвейерные методы используют в основном в качестве параметра функциональные объекты. 2050 | 2051 | Есть вещи, которые можно сделать с помощью блоков кода с `for`, но не получиться сделать из функциональных объектов в 2052 | Stream API: 2053 | 2054 | - Из блока кода можно прочесть или изменить любую локальную переменную в области видимости; из лямбда-выражения можно 2055 | прочесть только финальные или фактически финальные переменные, и нельзя изменять никакие локальные переменные. 2056 | - В блоке кода можно выполнить оператор `return` для выхода из охватывающего метода, прервать или продолжить выполнение 2057 | охватывающего цикла с помощью оператора `break` и `continuе`, или сгенерировать любое проверяемое исключение, которое 2058 | этот метод объявил как могущее быть сгенерированным; из лямбда-выражения нельзя сделать ни одно из перечисленных 2059 | действий. 2060 | 2061 | Но что можно сделать с помощью Stream API: 2062 | 2063 | - Единообразное преобразование последовательностей элементов. 2064 | - Фильтрование последовательностей элементов. 2065 | - Объединение последовательностей элементов с помощью единственной операции(например, сложение, конкатенация или 2066 | вычисление минимума). 2067 | - Накопление последовательности элементов в коллекции, возможно, с группировкой по некоторым общим атрибутам. 2068 | - Поиск в последовательности элементов некоторого элемента, удовлетворяющего некоторым критериям поиска. 2069 | 2070 | В Stream API сложно сделать одновременное обращение к элементам на разных этапах конвейера. Одним из способов обхода 2071 | этого является отображение каждого значения на объект пары, содержащий исходное и новое значения, но это решение не 2072 | является удовлетворительным, в особенности если такие объекты пар требуются для нескольких этапов конвейера. Такой код 2073 | беспорядочный и многословный. Когда это приемлемо и когда нужен доступ к значению на более ранней стадии вычислений, 2074 | лучше инвертировать использованное отображение. 2075 | 2076 | `static Stream primes(){return Stream.iterate(TWO,BigInteger::nextProbablePrime);}` 2077 | 2078 | Есть много задач, для которых не очевидно, что следует использовать: Stream API или итерации. Все сводится к личным 2079 | предпочтениям и среде разработки. 2080 | 2081 | Пример: 2082 | 2083 | ```java 2084 | public class Card { 2085 | // Iterative Cartesian product computation 2086 | private static List newDeckIterative() { 2087 | List result = new ArrayList<>(); 2088 | for (Suit suit : Suit.values()) 2089 | for (Rank rank : Rank.values()) result.add(new Card(suit, rank)); 2090 | return result; 2091 | } 2092 | 2093 | // Stream-based Cartesian product computation 2094 | private static List newDeckStream() { 2095 | return Stream.of(Suit.values()) 2096 | .flatMap(suit -> Stream.of(Rank.values()).map(rank -> new Card(suit, rank))) 2097 | .collect(toList()); 2098 | } 2099 | } 2100 | 2101 | ``` 2102 | 2103 | ## 7.5 Предпочитайте в потоках функции без побочных эффектов (Item 46) 2104 | 2105 | Stream API это не просто инструмент для работы со структурами данных, это целая парадигма основанная функциональном 2106 | программировании. Поэтому, чтобы получить выразительность, скорость и возможность параллельных вычислений, при работе с 2107 | потоками, нужно принять саму парадигму. 2108 | 2109 | Важная часть парадигмы Stream API это приближение результата вычислений к чистой функции. Чистая функция - это функция, 2110 | результат которой зависит только от входных данных, и при этом никак не зависит от какого-либо изменяемого состояния, 2111 | так и не обновляет никакое состояние. Для этого нужно добиться, что бы функциональные объекты потоковых операций, были 2112 | без побочных эффектов. А не маскировать интервальный код, под потоковое вычисление. 2113 | 2114 | *Входные данные не должны зависеть от изменяемого состояния указывает* - из лямбда-выражения можно прочесть только 2115 | финальные или фактически финальные переменные, и нельзя изменять никакие локальные 2116 | переменные ([Item 45](#74-разумно-используйте-stream-api-item-45)). 2117 | 2118 | Операция `forEach` должна использоваться только для того, чтобы вывести результат потоковых вычислений, но не для 2119 | выполнения вычислений. 2120 | 2121 | ```java 2122 | public class Freq { 2123 | public static void main(String[] args) { 2124 | 2125 | // Uses the streams API but not the paradigm--Don't do this! 2126 | Map freq = new HashMap<>(); 2127 | try (Stream words = new Scanner(file).tokens()) { 2128 | words.forEach( 2129 | word -> { 2130 | freq.merge(word.toLowerCase(), 1L, Long::sum); 2131 | }); 2132 | } 2133 | 2134 | // Proper use of streams to initialize a frequency table 2135 | Map freq; 2136 | try (Stream words = new Scanner(file).tokens()) { 2137 | freq = words.collect(groupingBy(String::toLowerCase, counting())); 2138 | } 2139 | } 2140 | } 2141 | 2142 | ``` 2143 | 2144 | Для того чтобы правильно использовать потоки, нужно лучше изучить `Collectors`. Наиболее 2145 | важные: `toList`, `toSet`, `toMap`, `groupingBy` и `joing`. 2146 | 2147 | ## 7.6 Предпочитайте коллекции потокам в качестве возвращаемых типов (Item 47) 2148 | 2149 | При написании метода, который возвращает последовательность элементов, клиенту может потребовать вернуть их как 2150 | коллекцию или вернуть их как поток. 2151 | 2152 | `Сollection` или подходящий подтип в общем случае является наилучшим типом возвращаемого значения для открытого метода, 2153 | возвращающего последовательность. Поскольку `Collection` подтип `Iterable` и обеспечивает итеративный доступ, а также 2154 | имеет метод `stream` и обеспечивает также же потоковый доступ. 2155 | 2156 | Если невозможно вернуть `Сollection`, то нужно вернуть `Stream` или `Iterable`, в зависимости от задачи. `Stream` не 2157 | расширяет `Iterable`, поэтому отсутствует итеративный доступ. Но можно написать свои адаптеры `Stream` - `Interable`: 2158 | 2159 | ```java 2160 | public class Adapters { 2161 | // Adapter from Stream to Iterable 2162 | public static Iterable iterableOf(Stream stream) { 2163 | return stream::iterator; 2164 | } 2165 | 2166 | // Adapter from Iterable to Stream 2167 | public static Stream streamOf(Iterable iterable) { 2168 | return StreamSupport.stream(iterable.spliterator(), false); 2169 | } 2170 | } 2171 | 2172 | ``` 2173 | 2174 | ## 7.7 Будьте внимательны при многопоточном использовании Stream API (Item 48) 2175 | 2176 | Написание многопоточных программ на языке Java становиться все проще, но написание правильных и быстрых многопоточных 2177 | программ остается таким же сложным, как и ранее. 2178 | 2179 | Что сделать конвейерные операции в Stream API многопоточными, нужно вызвать метод `parallel()`. 2180 | 2181 | Многопоточные конвейерные операции вряд ли увеличат производительность, если источник получен от `Stream.iterate` или 2182 | используется промежуточная операция `limit`. 2183 | 2184 | Не делайте многопоточные Stream API без надлежавшего разбора его работы. 2185 | 2186 | Выигрыш в производительности от применения параллелизма оказывается наибольшим в случае обработки 2187 | экземпляров `ArraysList`, `HashMap`, `HashSet` и `ConcurrentHashMap`; массивов; диапазоны `int` и `long`. Основные 2188 | факторы: 2189 | 2190 | - Эти структуры могут точно и дешево быть разделены на поддиапазоны любых желаемых размеров, что позволяет легко 2191 | разделить работу при многопоточности 2192 | - Они обеспечивают очень хорошую локальность ссылок при последовательной обработке: ссылки последовательных элементов 2193 | хранятся в памяти рядом одна с другой. 2194 | 2195 | Если значительное количество работы выполняется в завершающей операции, то мультипольный конвейер будет иметь 2196 | ограниченную эффективность. С точки зрения многопоточности, лучшие завершающие операции это 2197 | приведения: `reduce`, `min`, `count`, `sum`. А операции, выполняемые `collect`, плохие кандидаты для многопоточности 2198 | из-за накладные расходов объединения коллекции. 2199 | 2200 | Многопоточность Stream API может привести не только к низкой производительности, включая ошибки живучести, но и к 2201 | неверным результатам и непредсказуемому поведению (ошибкам безопасности). 2202 | 2203 | При соответствующих обстоятельствах можно достичь ускорения, почти линейно зависящего от количества ядер процессора, 2204 | просто добавив вызов `parallel` в конвейер потока. 2205 | 2206 | # 8 Методы 2207 | 2208 | ## 8.1 Проверяйте корректность параметров (Item 49) 2209 | 2210 | При написании метода или конструктора, нужно продумать какие ограничения имеются для его параметров. Необходимо отразить 2211 | эти ограничения в документации, а также реализовать в начале работы метода явную проверку их выполнения. А отказ от 2212 | этого может привести к нарушению принципа атомарности сбоев. 2213 | 2214 | В документации, исключения которые могут быть сгенерированы при неправильном параметре помечаются 2215 | дескриптором `@throws`([Item 56](#88-пишите-документирующие-комментарии-для-всех-открытых-элементов-api-item-56)). 2216 | Основные используемы для этого исключения: `IllegalArgumentException`, `IndexOutOfBoundsException` 2217 | и `NullPointerException`([Item 72](#104-предпочитайте-использовать-стандартные-исключения-item-72)). 2218 | 2219 | Метод `Objects.requireNonNull`, гибкий и удобный, так что больше нет никакой причины для проверки значения `null` 2220 | вручную. 2221 | 2222 | Для методов с модификатором доступа `private`, можно использовать проверку параметров с помощью `assert`: 2223 | 2224 | ```java 2225 | class SortUtil { 2226 | private static void sort(long a[], int offset, int length) { 2227 | 2228 | assert a != null; 2229 | assert offset >= 0 && offset <= a.length; 2230 | assert length >= 0 && length <= a.length - offset; 2231 | } 2232 | } 2233 | 2234 | ``` 2235 | 2236 | Отличия `assert` от обычных проверок: 2237 | 2238 | 1. Если условия не выполнится, то генерируется исключение `AssertionError`. 2239 | 2. Они не выполняют никаких действий и не имеют стоимости при их отключения с помощью флага командной строки `java -ea`. 2240 | 2241 | Важно проверять правильность параметров, которые методом не используются, а откладываются для последующей обработки. 2242 | 2243 | Можно не делать проверку, если она является дорогостоящей или непрактичной операцией, и при этом параметры неявно 2244 | проверяются непосредственно в процессе выполнения вычисления. 2245 | 2246 | Если в ходе вычисления методом происходит неявная проверка корректности параметра, и если проверку не проходит, то 2247 | генерируется исключение, которое не соответствует исключению из документации. Здесь можно использовать идиому трансляции 2248 | исключения ([Item 73](#105-генерируйте-исключения-соответствующие-абстракции-item-73)). 2249 | 2250 | ## 8.2 При необходимости создавайте защитные копии (Item 50) 2251 | 2252 | Нужно писать программы оборонительно - исходя из предположения, что клиенты вашего класса будут предпринимать все 2253 | возможное для того, чтобы разрушить его инварианты. 2254 | 2255 | Некорректный неизменяемый класс: 2256 | 2257 | ```java 2258 | public final class Period { 2259 | private final Date start; 2260 | private final Date end; 2261 | 2262 | /** 2263 | * @param start the beginning of the period 2264 | * @param end the end of the period; must not precede start 2265 | * @throws IllegalArgumentException if start is after end 2266 | * @throws NullPointerException if start or end is null 2267 | */ 2268 | public Period(Date start, Date end) { 2269 | if (start.compareTo(end) > 0) throw new IllegalArgumentException(start + " after " + end); 2270 | this.start = start; 2271 | this.end = end; 2272 | } 2273 | 2274 | public Date start() { 2275 | return start; 2276 | } 2277 | 2278 | public Date end() { 2279 | return end; 2280 | } 2281 | } 2282 | 2283 | ``` 2284 | 2285 | Атака - изменение внутренних данных: 2286 | 2287 | ```java 2288 | public class Example { 2289 | void method() { 2290 | 2291 | Date start = new Date(); 2292 | Date end = new Date(); 2293 | Period p = new Period(start, end); 2294 | end.setYear(78); // Modifies internals of p! 2295 | } 2296 | } 2297 | 2298 | ``` 2299 | 2300 | В частном случае `Date` является устаревшим классом и не должен испльзоваться в новом коде. 2301 | 2302 | В общем случае для защиты необходиом сделать защитную копию каждого изменяемого параметра конструктора: 2303 | 2304 | ```java 2305 | public Period(Date start, Date end) { 2306 | this.start = new Date(start.getTime()); 2307 | this.end = new Date(end.getTime()); 2308 | 2309 | if (this.start.compareTo(this.end) > 0) 2310 | throw new IllegalArgumentException( 2311 | this.start + " after " + this.end); 2312 | } 2313 | 2314 | ``` 2315 | 2316 | Копии делаются до проверки параметров ([Item 49](#81-проверяйте-корректность-параметров-item-49)), так что проверка 2317 | корректности выполняется над копией, а не над оригиналом. Это позволяет защитить от атаки между проверкой и 2318 | использованием. 2319 | 2320 | Не используйте `clone` для создания копии параметров, тип которого позволяет ненадежным сторонам создавать подклассы. 2321 | 2322 | Вторая атака - изменение внутренних данных, через методы доступа: 2323 | 2324 | ```java 2325 | public class Example { 2326 | void method() { 2327 | Date start = new Date(); 2328 | Date end = new Date(); 2329 | p = new Period(start, end); 2330 | 2331 | p.end().setYear(78); // Modifies internals of p! 2332 | } 2333 | } 2334 | 2335 | ``` 2336 | 2337 | Для защиты, нужно возвращать копии изменяемых внутренних объектов: 2338 | 2339 | ```java 2340 | public Date start() { 2341 | return new Date(start.getTime()); 2342 | } 2343 | 2344 | public Date end() { 2345 | return new Date(end.getTime()); 2346 | } 2347 | 2348 | ``` 2349 | 2350 | Лучше использовать неизменяемые объекты ([Item 17](#43-минимизируйте-изменяемость-item-17)) в качестве компонентов. 2351 | 2352 | Возможно будет снижение производительности из-за создания копии. По-этому необходимо анализировать: если вы доверяете 2353 | вызываемому объекту, то можно копии не делать. 2354 | 2355 | ## 8.3 Тщательно проектируйте сигнатуры методов (Item 51) 2356 | 2357 | Советы по проектированию API: 2358 | 2359 | - Тщательно выбирайте имена методов ([Item 68](#912-придерживайтесь-общепринятых-соглашений-по-именованию-item-68)) 2360 | - Не заходите слишком далеко в погоне за удобством методов. Не создавайте их слишком много 2361 | - Избегайте длинных списков параметров. 4 параметра - это максимум. Особенно вредны длинные последовательности 2362 | параметров одного и того же типа. Что сократить есть следующие приемы: разбить метод на подметоды; создать 2363 | вспомогательный класс, хранящий группы параметров - обычно используют вложенный статический 2364 | класс ([Item 24](#410-предпочитайте-вложенный-статический-класс-вложенному-внутреннему-классу-item-24)); использовать 2365 | шаблон 2366 | строитель ([Item 2](#22-при-большом-количестве-параметров-конструктора-подумайте-о-проектном-шаблоне-строитель-item-2)). 2367 | - Предпочитайте в качестве типов параметров интерфейсы, а не 2368 | классы ([Item 22](#48-используйте-интерфейсы-только-для-определения-типов-item-22)) 2369 | - Предпочитайте двухэлементные типы перечислений для 2370 | параметров `boolean`: `public enum TemperatureScale {CELSIUS, FARENHEIT}` 2371 | 2372 | ## 8.4 Перезагружайте методы разумно (Item 52) 2373 | 2374 | Выбор среди перезагруженных методов является статическим, в то время как выбор переопределенного метода - динамический. 2375 | 2376 | ```java 2377 | public class CollectionClassifier { 2378 | public static String classify(Set s) { 2379 | return "Set"; 2380 | } 2381 | 2382 | public static String classify(List lst) { 2383 | return "List"; 2384 | } 2385 | 2386 | public static String classify(Collection c) { 2387 | return "Unknown Collection"; 2388 | } 2389 | 2390 | public static void main(String[] args) { 2391 | Collection[] collections = { 2392 | new HashSet(), new ArrayList(), new HashMap().values() 2393 | }; 2394 | 2395 | for (Collection c : collections) 2396 | System.out.println( 2397 | classify(c)); // -> Unknown Collection /n Unknown Collection /n Unknown Collection 2398 | } 2399 | } 2400 | 2401 | ``` 2402 | 2403 | В данном случае выбрался наиболее общий тип коллекции, который указан в цикле. 2404 | 2405 | Выбор того, какая из перезагрузок будет вызвана, выполняется во время компиляции. 2406 | 2407 | При переопределении метода, во время выполнения программы выбирается наиболее конкретный тип: 2408 | 2409 | ```java 2410 | class Wine { 2411 | String name() { 2412 | return "wine"; 2413 | } 2414 | } 2415 | 2416 | class SparklingWine extends Wine { 2417 | @Override 2418 | String name() { 2419 | return "sparkling wine"; 2420 | } 2421 | } 2422 | 2423 | class Champagne extends SparklingWine { 2424 | @Override 2425 | String name() { 2426 | return "champagne"; 2427 | } 2428 | } 2429 | 2430 | public class Overriding { 2431 | public static void main(String[] args) { 2432 | Wine[] wines = { 2433 | new Wine(), new SparklingWine(), new Champagne() 2434 | }; 2435 | for (Wine wine : wines) 2436 | System.out.println(wine.name()); // prints: wine, sparkling wine, and champagne 2437 | } 2438 | } 2439 | ``` 2440 | 2441 | Что бы в классе `CollectionClassifier` тип распознавался во время выполнения. Нужно использовать один метод и 2442 | оператор `instanceof`: 2443 | 2444 | ```java 2445 | public class FixedCollectionClassifier { 2446 | public static String classify(Collection c) { 2447 | return c instanceof Set ? "Set" : c instanceof List ? "List" : "Unknown Collection"; 2448 | } 2449 | } 2450 | 2451 | ``` 2452 | 2453 | Безопасная и консервативная стратегия состоит в том, чтобы никогда не экспортировать две перегрузки с одинаковым числом 2454 | параметров. При переменном количестве аргументов - не перезагружать вовсе. 2455 | 2456 | Можно всегда дать методам разные имена вместо того, чтобы их перезагружать. 2457 | 2458 | Если перезагруженные методы имеют одинаковое количество параметров, но имеют "совершенно иные" типы, например `int` 2459 | и `Collection` - такое допускается. 2460 | 2461 | Не перегружайте методы, принимающие различные функциональные интерфейсы с одной и той же позиции аргумента. 2462 | 2463 | При эволюции классов бывает необходимость нарушить рекомендации выше. Например, в Java 5 2464 | метод `String::contentEquals(StringBuffer)` переопределяется с типом параметра `CharSequence`, который является общим 2465 | для других `String`-классов. Что бы для клиента разница в использовании не была замена, необходимо сохранить поведение и 2466 | у старого метода вызывать новый понижением типа: 2467 | 2468 | ```java 2469 | 2470 | public boolean contentEquals(StringBuffer sb) { 2471 | return contentEquals((CharSequence) sb); 2472 | } 2473 | 2474 | ``` 2475 | 2476 | ## 8.5 Используйте методы с переменным количеством аргументов с осторожностью (Item 53) 2477 | 2478 | Методы с переменным количеством аргументов, также называется методы с переменной арности, принимают нуль или более 2479 | аргументов: 2480 | 2481 | ```java 2482 | public class Varargs { 2483 | // The WRONG way to use varargs to pass one or more arguments! 2484 | static int min(int... args) { 2485 | if (args.length == 0) throw new IllegalArgumentException("Too few arguments"); 2486 | int min = args[0]; 2487 | for (int i = 1; i < args.length; i++) if (args[i] < min) min = args[i]; 2488 | return min; 2489 | } 2490 | } 2491 | 2492 | ``` 2493 | 2494 | Если будет вызов без аргументов, то ошибка будет во время компиляции, а не выполнения и необходимо делать доп. проверку. 2495 | 2496 | Альтернатива этому - создать метод с двумя параметрами: 2497 | 2498 | ```java 2499 | public class Varargs { 2500 | static int min(int firstArg, int... remainingArgs) { 2501 | int min = firstArg; 2502 | for (int arg : remainingArgs) if (arg < min) min = arg; 2503 | return min; 2504 | } 2505 | } 2506 | 2507 | ``` 2508 | 2509 | Использование параметров переменной длины могут влиять на производительность. Можно определить с каким количеством 2510 | параметров вызывается метод наиболее часто и сделать несколько перезагрузок метода с количеством аргументов от 2511 | нуля - до популярного значения. Например, как это реализовано в `List.of()`. 2512 | 2513 | ## 8.6 Возвращайте пустые массивы и коллекции, а не null (Item 54) 2514 | 2515 | Если метод, возвращающий коллекцию, может вернуть `null` это требует от клиента добавления проверки на `null`. 2516 | 2517 | Аргументы против `null` позволяет избежать расходов на размещение в памяти пустого контейнера: 2518 | 2519 | - На этом уровне нет смысле беспокоится о производительности 2520 | - Можно возвращать пустой контейнер без выделения памяти. Например, `Collections.emptyList` 2521 | 2522 | ## 8.7 Возвращайте Optional с осторожностью (Item 55) 2523 | 2524 | Если метод не сможет вернуть значение, есть следующие подходы для реализации данной ситуации: 2525 | 2526 | - Вернуть `null` 2527 | - Сгенерировать исключение 2528 | - Начиная с Java 8 вернуть `Optional` 2529 | 2530 | Объекты `Optional` по духу аналогичны проверяемым 2531 | исключениям ([Item 70](#102-используйте-для-восстановления-проверяемые-исключения-а-для-программных-ошибок---непроверяемые-item-70)) - 2532 | заставляют клиента признать, что возвращаемого значения может и не быть, и требует дополнительного стереотипного кода 2533 | клиента. Необрабатываемые исключения и `null` позволяют игнорировать этот случай, что чревато плохими последствиями. 2534 | 2535 | Никогда не возвращайте значение `null` из метода, возвращающего `Optional`: тем самым теряется сам смысл его 2536 | использования. 2537 | 2538 | Типы контейнеров, включая коллекции, `Map`, `Stream`, массивы и `Optional`, не должны оборачиваться в `Optional`. 2539 | 2540 | Следует объявлять метод как возвращающий `Optional` если он не в состоянии возвратить результат, а клиенты должны 2541 | выполнить специальную обработку, когда результат не возвращается. Но возврат `Optional` может влиять на 2542 | производительность. 2543 | 2544 | Никогда не следует возвращать `Optional` для упакованных примитивных типов, для этого следует 2545 | использовать: `OptionalInt`, `OptionalLong` и `OptionalDouble`. 2546 | 2547 | Не следует использовать `Optional` как ключ, значение или элемент, коллекции или массива. 2548 | 2549 | ## 8.8 Пишите документирующие комментарии для всех открытых элементов API (Item 56) 2550 | 2551 | Чтобы должным образом документировать свой API, следует предварять каждый экспортируемый класс, интерфейс, конструктор, 2552 | метод и объявление поля документирующим комментарием. 2553 | 2554 | Если класс поддерживает сериализацию, следует также документировать его сериализированную 2555 | форму ([Item 87](#123-подумайте-о-применении-пользовательской-сериализованной-формы-item-87)). 2556 | 2557 | Открытые классы не должны использовать конструкторы по-умолчанию, потому что нет никакого способа предоставить для них 2558 | документирующих комментарии. 2559 | 2560 | Документирующий комментарий метода должен лаконично описывать контракт между этим методом и его клиентом: 2561 | 2562 | - Контракт должен точно оговаривать, что делает данный метод, а не как он это делает 2563 | - Необходимо перечислить все предусловия и постусловия 2564 | - Обычно предусловия неявно описывается дескриптором `@throws` для непроверяемых исключений 2565 | - Предусловия могут указываться вместе с параметрами, которых они касаются, в соответствующих параметрах `@param` 2566 | - Должны быть документированы любые побочные эффекты. Побочный эффект - это наблюдаемое изменение состояние системы, 2567 | которое является неочевидным условием для достижения постусловия. 2568 | - Каждый комментарий должен включать в себя дескрипторы: `@param`, `@return`, `@throws`. 2569 | 2570 | ```java 2571 | public class DocExamples { 2572 | 2573 | /** 2574 | * Returns the element at the specified position in this list. 2575 | * 2576 | *

This method is not guaranteed to run in constant time. In some implementations it may 2577 | * run in time proportional to the element position. 2578 | * 2579 | * @param index index of element to return; must be non-negative and less than the size of this 2580 | * list 2581 | * @return the element at the specified position in this list 2582 | * @throws IndexOutOfBoundsException if the index is out of range ({@code index < 0 || index >= 2583 | * this.size()}) 2584 | */ 2585 | E get(int index) { 2586 | return null; 2587 | } 2588 | 2589 | // Use of @implSpec to describe self-use patterns & other visible implementation details. 2590 | 2591 | /** 2592 | * Returns true if this collection is empty. 2593 | * 2594 | * @implSpec This implementation returns {@code this.size() == 0}. 2595 | * @return true if this collection is empty 2596 | */ 2597 | public boolean isEmpty() { 2598 | return false; 2599 | } 2600 | 2601 | // Use of the @literal tag to include HTML and javadoc metacharacters in javadoc comments. 2602 | 2603 | /** A geometric series converges if {@literal |r| < 1}. */ 2604 | public void fragment() { 2605 | } 2606 | 2607 | // Controlling summary description when there is a period in the first "sentence" of doc comment. 2608 | 2609 | /** A suspect, such as Colonel Mustard or {@literal Mrs. Peacock}. */ 2610 | public enum FixedSuspect { 2611 | MISS_SCARLETT, 2612 | PROFESSOR_PLUM, 2613 | MRS_PEACOCK, 2614 | MR_GREEN, 2615 | COLONEL_MUSTARD, 2616 | MRS_WHITE 2617 | } 2618 | 2619 | // Generating a javadoc index entry in Java 9 and later releases. 2620 | 2621 | /** This method complies with the {@index IEEE 754} standard. */ 2622 | public void fragment2() { 2623 | } 2624 | 2625 | // Documenting enum constants (Page 258) 2626 | 2627 | /** An instrument section of a symphony orchestra. */ 2628 | public enum OrchestraSection { 2629 | /** Woodwinds, such as flute, clarinet, and oboe. */ 2630 | WOODWIND, 2631 | 2632 | /** Brass instruments, such as french horn and trumpet. */ 2633 | BRASS, 2634 | 2635 | /** Percussion instruments, such as timpani and cymbals. */ 2636 | PERCUSSION, 2637 | 2638 | /** Stringed instruments, such as violin and cello. */ 2639 | STRING; 2640 | } 2641 | 2642 | // Documenting an annotation type 2643 | 2644 | /** 2645 | * Indicates that the annotated method is a test method that must throw the designated exception 2646 | * to pass. 2647 | */ 2648 | @Retention(RetentionPolicy.RUNTIME) 2649 | @Target(ElementType.METHOD) 2650 | public @interface ExceptionTest { 2651 | /** 2652 | * The exception that the annotated test method must throw in order to pass. (The test is 2653 | * permitted to throw any subtype of the type described by this class object.) 2654 | */ 2655 | Class value(); 2656 | } 2657 | } 2658 | 2659 | ``` 2660 | 2661 | В документирующем комментарии слово `this` всегда указывает на объект, которому принадлежит вызываемый метод. 2662 | 2663 | При создании класса для наследования необходимо документировать его схемы использования собственных 2664 | объектов ([Item 19](#45-проектируйте-и-документируйте-наследование-либо-запрещайте-его-item-19)), это 2665 | обычно документируется с помощью `@implSpec`. 2666 | 2667 | Документирующие комментарии должны быть читаемыми как в исходном тексте, так и в сгенерированной документации. 2668 | 2669 | Первым предложением комментария является краткое описание того элемента, к которому этот комментарий относится. 2670 | 2671 | Никакие два члена или конструктора в одном классе или интерфейсе не должны иметь одинаковое краткое описание. 2672 | 2673 | При документировании обобщенного типа или метода убедитесь, что вы документируете все параметры. 2674 | 2675 | При документировании типа перечисления убедитесь, что вы документируете константы. 2676 | 2677 | При документировании типа аннотации убедитесь, что вы документируете все члены. 2678 | 2679 | Документирующие комментарии уровня пакета следует помещать в файл c именем `package-info.java`. 2680 | 2681 | Является ли класс безопасным с точки зрения многопоточности или нет - в любом случае уровень его безопасности с точки 2682 | зрения потоков должен быть документирован ([Item 82](#115-документируйте-безопасность-с-точки-зрения-потоков-item-82)). 2683 | Если класс сериализуется, нужно документировать сериализованный 2684 | тип ([Item 87](#123-подумайте-о-применении-пользовательской-сериализованной-формы-item-87)). 2685 | 2686 | # 9 Общие вопросы программирования 2687 | 2688 | ## 9.1 Минимизируйте область видимости локальных переменных (Item 57) 2689 | 2690 | Наиболее мощный способ минимизировать область видимости локальной переменной заключается в её объявлении там, где она 2691 | впервые используются. 2692 | 2693 | Почти каждое объявление локальной переменной должно содержать инициализатор. 2694 | 2695 | Следует предпочитать цикл `for` циклу `while` при условии, что после завершения цикла содержимое переменной цикла не 2696 | требуется. 2697 | 2698 | Так же для уменьшения области видимости локальных переменных, заключается в сохранении малого размера и точной 2699 | направленности методов. 2700 | 2701 | ## 9.2 Предпочитайте циклы for для коллекции традиционным циклам for (Item 58) 2702 | 2703 | Идиомы обхода коллекции или массива с использованием `for`: 2704 | 2705 | ```java 2706 | class ForIdiom { 2707 | public static void main(String[] args) { 2708 | 2709 | // Классическая идиома обхода с использованием переменной для индекса 2710 | for (int i = 0, n = expensiveComputation(); i < n; i++) { 2711 | } 2712 | 2713 | // Идиома обхода в случае необходимости доступа к итератору 2714 | for (Iterator i = c.iterator(); i.hasNext(); ) { 2715 | } 2716 | 2717 | // Предпочтительная идиома обхода коллекции 2718 | for (Element e : c) { 2719 | } 2720 | } 2721 | } 2722 | 2723 | ``` 2724 | 2725 | Минус первых двух идиом: 2726 | 2727 | - Итераторы и индексные переменные создают лишние помехи - для работы нужны только сами элементы 2728 | - Итераторы в цикле встречаются 3 раза, а индексная переменная 4 раза. Можно использовать неверную переменную 2729 | - Излишние обращают внимание на тип контейнера и создают сложности при изменении типа 2730 | 2731 | Когда нельзя применить цикл по коллекциям: 2732 | 2733 | - **Деструктивная фильтрация** - если нужно удалить элемент из коллекции, то нужно использовать явный итератор. Начиная 2734 | с Java 8 можно попробовать метод метод `removeIf` 2735 | - **Преобразования** - если требует заменить элементы, то нужно использовать итератор или индекс массива 2736 | - **Параллельное итерирование** - если нужен параллельный обход нескольких коллекций, требуется явное управление 2737 | итератором или индексной переменной 2738 | 2739 | ## 9.3 Изучите и используйте возможности библиотек (Item 59) 2740 | 2741 | Преимущества использования библиотек: 2742 | 2743 | - Вместе со стандартной библиотекой вы используете знания написавших её экспертов, а также опыт тех, кто работал с ней 2744 | до вас 2745 | - Не нужно терять время на написание решений для разовых задач, имеющих лишь косвенное отношение к вашей работе 2746 | - Производительность имеет тенденцию со временем повышаться, причем без каких-либо усилий с вашей стороны 2747 | 2748 | **Не изобретайте велосипед!** 2749 | 2750 | С каждой новой версией в библиотеки включается множество новых функций, и стоит быть в курсе всех новшеств. 2751 | 2752 | Каждый программист должен знать основные возможности пакетов `java.lang`, `java.util`, `java.io`, а также их 2753 | подпакетов. А также библиотеки для многопоточности `java.util.concurrent`. 2754 | 2755 | ## 9.4 Если вам нужны точные ответы, избегайте float и double (Item 60) 2756 | 2757 | Для вычисления, требующих точного результата, в том числе и для финансовых, не используйте типы с плавающей точкой - 2758 | `float` и `double`. 2759 | 2760 | Как альтернатива класс `BigDecimal`(18+ цифр): отслеживает положение десятичной точки, полный контроль над 2761 | округлением (можно выбрать режим). 2762 | 2763 | Если требуется большая производительность и не пугает самостоятельное отслеживание десятичной точки, а обрабатываемые 2764 | значения не слишком велики можно использовать `int`(<=9 цифр) и `long`(<=18 цифр). 2765 | 2766 | ## 9.5 Предпочитайте примитивные типы упакованных примитивным типам (Item 61) 2767 | 2768 | В Java переменные бывают двух типов: примитивные(`int`, `double`, `boolean` и др.) и ссылочные(`String`, `List` и др.). 2769 | Для каждого примитивного типа есть аналог ссылочного типа, например `int` - `Integer`. Данные классы называются - 2770 | упакованные примитивные типы. 2771 | 2772 | Основные различия примитивных и упакованных типов: 2773 | 2774 | - У примитивных типов есть только значения, а у упакованных ещё и идентичность 2775 | - Примитивные типы имеют только полнофункциональные значения, а упакованные могут иметь нефункциональные - `null` 2776 | - Примитивные типы эффективны с точки зрения потребления памяти и времени работы, чем упакованные 2777 | 2778 | Применение оператора `==` к упакованным примитивным типа почти всегда ошибочно, лучше сравнивать через `equals`. 2779 | 2780 | При смешивании обычных и упакованных примитивных типов в одной операции - упакованных примитивный типа автоматически 2781 | распаковывается. Если объект нулевой (`null`), то возможно сгенерируется исключение `NullPointerException`. Также из-за 2782 | распаковки, может снижаться производительность. 2783 | 2784 | Когда использовать упакованные типы: 2785 | 2786 | - В качестве элементов, ключей и значений коллекций 2787 | - В качестве параметров в параметризованных типах ([Item 26](#51-не-используйте-сырые-типы-item-26)) 2788 | - При вызове рефлексивных методов ([Item 65](#99-предпочитайте-интерфейсы-рефлексии-item-65)) 2789 | 2790 | ## 9.6 Избегайте применения строк там, где уместнее другой тип (Item 62) 2791 | 2792 | Класс `String` предназначен для представления текста. 2793 | 2794 | Когда его не следует применять: 2795 | 2796 | - `String` - плохая замена другим типам значений. При парсинге лучше сразу преобразовать в нужный тип 2797 | - `String` - плохая замена для перечислений ([Item 34](#61-используйте-перечисление-вместо-констант-int-item-34)) 2798 | - `String` - плохая замена для агрегатных типов. Например, `String key = className + "#" + i.next();` 2799 | - `String` - плохая замена надежным (устойчивым к подделке) ключам 2800 | 2801 | ## 9.7 Помните о проблемах производительности при конкатенации строк (Item 63) 2802 | 2803 | Время, которое необходимо оператору конкатенации для последовательного объединения n объектов `String`, пропорционально 2804 | квадрату n. Это следует из-за неизменяемого класса ([Item 17](#43-минимизируйте-изменяемость-item-17)). 2805 | 2806 | Низкая производительность при объединении большого количеств строк: 2807 | 2808 | ```java 2809 | class Concatenation { 2810 | // Inappropriate use of string concatenation - Performs horribly! 2811 | public String statement() { 2812 | String result = ""; 2813 | for (int i = 0; i < numItems(); i++) result += lineForItem(i); 2814 | return result; 2815 | } 2816 | } 2817 | 2818 | ``` 2819 | 2820 | Применение `StringBuilder` для повышения производительности: 2821 | 2822 | ```java 2823 | class Concatenation { 2824 | public String statement() { 2825 | StringBuilder b = new StringBuilder(numItems() * LINE_WIDTH); 2826 | for (int i = 0; i < numItems(); i++) b.append(lineForItem(i)); 2827 | return b.toString(); 2828 | } 2829 | } 2830 | 2831 | ``` 2832 | 2833 | ## 9.8 Для ссылки на объекты используйте их интерфейсы (Item 64) 2834 | 2835 | Если имеются подходящие типы интерфейсов, то параметры, возвращаемые значения, переменные и поля следует объявлять, 2836 | используя типы интерфейсов. 2837 | 2838 | Плохо - испльзование классов в качестве типов: 2839 | 2840 | `ArraysList subscribers = new ArraysList<>();` 2841 | 2842 | Хорошо - использование интерфейсов в качестве типов: 2843 | 2844 | `List subscribers=new ArraysList<>();` 2845 | 2846 | Если вы выработаете привычку использовать в качестве типов интерфейсы, ваша программа будет более гибкой. 2847 | 2848 | Можно изменить объект не меняя переменную: 2849 | 2850 | `List subscribers=new LinkedList<>();` 2851 | 2852 | Если исходная реализация интерфейса предлагала некоторую особенную функциональность, не предусмотренную общим контрактом 2853 | этого интерфейса, а код зависел от этой функциональности, крайне важно, чтобы новая реализация интерфейса обеспечивала 2854 | ту же функциональность. 2855 | 2856 | Вполне допустимо ссылаться на объект с использованием класса, а не интерфейса, если подходящий интерфейс отсутствует: 2857 | 2858 | - Классы значений: `String`, `BigInteger` и др. 2859 | - Классы фреймворков 2860 | - Если класс, реализующий интерфейс, предоставляет дополнительные методы, которых нет в интерфейсе 2861 | 2862 | ## 9.9 Предпочитайте интерфейсы рефлексии (Item 65) 2863 | 2864 | Ядро средств рефлексии, `java.lang.reflect`, предоставляет программный доступ к произвольным классам. 2865 | 2866 | Можно получить доступ через тип `Class` к `Constructor`, `Method` и `Field`. А также манипулировать классами/объектами. 2867 | 2868 | Рефлексия позволяет одному классу использовать другой класс, даже если последний во время компиляции первого ещё не 2869 | существовал. Цена данного подхода: 2870 | 2871 | - Вы теряете все преимущества проверки типов времени компиляции 2872 | - Код, необходимый для рефлексивного доступа к классам, неуклюжий и многословный 2873 | - Проблемы с производительностью 2874 | 2875 | Вы можете без особых затрат воспользоваться многими преимуществами рефлексии, применяя её в очень ограниченном виде. 2876 | 2877 | Во многих программах нужный класс отсутствует во время компиляции, для ссылки на него можно использовать подходящий 2878 | интерфейс или суперкласс. А экземпляры создавать рефлексивно, а затем обращаться к ним, как обычно, используя их 2879 | интерфейс или суперкласс. 2880 | 2881 | ```java 2882 | public class ReflectiveInstantiation { 2883 | // Reflective instantiation with interface access 2884 | public static void main(String[] args) { 2885 | // Translate the class name into a Class object 2886 | Class> cl = null; 2887 | try { 2888 | cl = 2889 | (Class>) // Unchecked cast! 2890 | Class.forName(args[0]); 2891 | } catch (ClassNotFoundException e) { 2892 | fatalError("Class not found."); 2893 | } 2894 | 2895 | // Get the constructor 2896 | Constructor> cons = null; 2897 | try { 2898 | cons = cl.getDeclaredConstructor(); 2899 | } catch (NoSuchMethodException e) { 2900 | fatalError("No parameterless constructor"); 2901 | } 2902 | 2903 | // Instantiate the set 2904 | Set s = null; 2905 | try { 2906 | s = cons.newInstance(); 2907 | } catch (IllegalAccessException e) { 2908 | fatalError("Constructor not accessible"); 2909 | } catch (InstantiationException e) { 2910 | fatalError("Class not instantiable."); 2911 | } catch (InvocationTargetException e) { 2912 | fatalError("Constructor threw " + e.getCause()); 2913 | } catch (ClassCastException e) { 2914 | fatalError("Class doesn't implement Set"); 2915 | } 2916 | 2917 | // Exercise the set 2918 | s.addAll(Arrays.asList(args).subList(1, args.length)); 2919 | System.out.println(s); 2920 | } 2921 | 2922 | private static void fatalError(String msg) { 2923 | System.err.println(msg); 2924 | System.exit(1); 2925 | } 2926 | } 2927 | 2928 | ``` 2929 | 2930 | Это технология достаточно мощная для реализации фреймворка. 2931 | 2932 | Пример выше демонстрирует два недостатка рефлексии: 2933 | 2934 | - Пример может генерировать шесть различных исключений во время выполнения, а без рефлексии может выявлена во время 2935 | компиляции 2936 | - Для создания экземпляра через рефлексию требуется несколько десятков строк кода, а для использования конструктора - 2937 | одна 2938 | 2939 | ## 9.10 Пользуйтесь машинно-зависимыми методами осторожно (Item 66) 2940 | 2941 | Интерфейс `Java Native Interface` позволяет вызывать машинно-зависимые методы, которые написанные на 2942 | машинно-зависимых языках программирования, таких как `C` или `C++`. 2943 | 2944 | Такие методы имели три основных предназначения: 2945 | 2946 | - Обеспечивали доступ к средствам платформы, например реестр `Windows` 2947 | - Обеспечивали доступ к существующим библиотекам с машинным кодом 2948 | - Эти методы использовались при написании критических в плане производительности фрагментов приложения 2949 | 2950 | Но по мере развития платформы `Java` их использование редко необходимо. Использовать машинно-зависимые методы для 2951 | повышения производительности редко целесообразно. 2952 | 2953 | Применение машинно-зависимых методов имеют недостатки: 2954 | 2955 | - Приложения становятся восприимчивы к ошибкам, связанные с памятью 2956 | - Для новой платформы машинно-зависимых программный код необходимо компилировать заново 2957 | - Труднее отлаживать 2958 | - При отсутствии опыта применение этих методов может привести к снижению производительности. Сборщик мусора не может 2959 | отслеживать применение таких методов 2960 | - Требуют дополнительного "склеивающего кода", который сложно писать и читать 2961 | 2962 | ## 9.11 Оптимизируйте осторожно (Item 67) 2963 | 2964 | Не жертвуйте надежными архитектурными принципами во имя производительности. Старайтесь писать хорошие, а не быстрые 2965 | программы. Если программа работает недостаточно быстро, правильная архитектура позволит её оптимизировать. 2966 | 2967 | Старайтесь избегать проектных решений, ограничивающих производительность. Рассматривайте влияние на производительность 2968 | проектных решений, на которых основан ваш API. Не следует изменять и искажать API достижения высокой производительности. 2969 | 2970 | Измеряйте производительность до и после каждой попытки оптимизации. Если после измерения программа медленная - найдите 2971 | источник проблемы с помощью профайлера. 2972 | 2973 | Для оптимизации изучите выбор алгоритма: никакая низкоуровневая оптимизация не сможет компенсировать плохой выбор 2974 | алгоритма. 2975 | 2976 | ## 9.12 Придерживайтесь общепринятых соглашений по именованию (Item 68) 2977 | 2978 | Группы соглашения по наименованию: 2979 | 2980 | **Типографические** 2981 | 2982 | | Тип | Пример | 2983 | |---------------------------|-----------------------------------------------| 2984 | | Пакет | com.google.inject, org.joda.time.format | 2985 | | Класс/Интерфейс | Timer, FutureTask, LinkedHashMap, HttpServlet | 2986 | | Метод/поле | remove, ensureCapacity, getCrc | 2987 | | Константы | MIN_VALUE, NEGATIVE_INFINITY | 2988 | | Локальные переменные | i, xref, houseNumber | 2989 | | Формальные параметры типа | T, E, K, V, X, T1, T2 | 2990 | 2991 | **Грамматические** 2992 | 2993 | | Тип | Соглашение | Пример | 2994 | |-----------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------| 2995 | | Классы инстанцируемые, Enum, Интерфейсы | Существительные в единственным числе или именные группы | Thread, PriorityQueue, ChessPiece, Collection, Comparator | 2996 | | Классы утильные | Существительные во множественном числе | Collectors, Collections | 2997 | | Интерфейсы | Прилагательные с окончанием -able или -ible | Runnable, Iterable, Accessible | 2998 | | Аннотации | Так как у аннотаций много вариантов использования - можно разные части речи | BindingAnnotation, Inject, ImplementedBy, Singleton | 2999 | | Методы выполняющие действия | Глаголы или глагольные конструкции | append, drawImage | 3000 | | Методы возвращающие булевые значения | Сначала слово is(реже has), потом существительное, именная группа, любое слово или фраза в роле прилагательного | isDigit, isProbablePrime, isEmpty, isEnabled, hasSiblings | 3001 | | Методы возвращающие не булевые значения | Существительное, именная группа, глагольная группа начинающаяся с get | size, hashCode, getTime | 3002 | | Методы конвертации типов | Именуются toType | toString, toArray | 3003 | | Методы возвращающие представление типа, отличный от вызываемого | asType | asList | 3004 | | Методы возвращающие примитивные типы с тем же значением | typeValue | intValue | 3005 | | Статистические фабрики | см. [Item 1](#21-рассмотрите-применение-статических-фабричных-методов-вместо-конструкторов-item-1) | valueOf, of, getInstance, newInstance, getType, newType | 3006 | | Поля булевые | Также как методы, но без is или has | initialized, composite | 3007 | | Поля не булевые | Существительные или именные группы | height, digits, bodyStyle | 3008 | | Локальные переменные | Аналогично для полей, но соблюдение менее обязательно | | 3009 | 3010 | # 10 Исключения 3011 | 3012 | ## 10.1 Используйте исключения только в исключительных ситуациях (Item 69) 3013 | 3014 | Исключения, как и подразумевает их название, должны применяться лишь для исключительных ситуаций; для обычного 3015 | управления потоком выполнения их не следует использовать никогда. 3016 | 3017 | Хорошо спроектированный API не должен заставлять своих клиентов использовать исключения для обычного управления потоком 3018 | выполнения. 3019 | 3020 | ## 10.2 Используйте для восстановления проверяемые исключения, а для программных ошибок - непроверяемые (Item 70) 3021 | 3022 | * Проверяемые исключения `Exception` - ситуации, которые разработчик никак не может предотвратить, например, не 3023 | получилось закрыть файловый дескриптор или отослать письмо, и исключение является одним из вариантов нормальной работы 3024 | кода. 3025 | * Непроверяемые исключения 3026 | * Ошибки время выполнения `RuntimeException` - ситуации, когда основной причиной ошибки является сам разработчик, 3027 | например, происходит обращение к `null` ссылке, деление на ноль, выход за границы массива и т.д. При этом 3028 | исключение не является одним из вариантов нормальной работы кода. 3029 | * Системные ошибки `Error` - критические ошибки, аварийные ситуации, после которых мы с трудом или вообще не в 3030 | состоянии продолжить работу. Например, закончилась память, переполнился стек вызовов и т.д. Ошибки зарезервированы 3031 | при использовании `JVM`, чтобы указывать на дефицит ресурсов, поэтому лучше не создавать новых классов `Error`. 3032 | 3033 | Если возникают сомнения, является ли исключение рабочим моментом приложения или программной ошибкой - генерируйте 3034 | непроверяемые исключения. 3035 | 3036 | Не создавайте классы исключений, которые не являются ни `Exception`, ни `RuntimeException`. 3037 | 3038 | Для классов исключения, можно создавать вспомогательные методы, которые снабжает исключение дополнительной информацией о 3039 | ситуации. 3040 | 3041 | ## 10.3 Избегайте ненужных проверяемых исключений (Item 71) 3042 | 3043 | Проверяемые исключения заставляют программиста работать с возникшей проблемой. 3044 | 3045 | Проверяемые исключения следует использовать при условии: 3046 | 3047 | - Предотвратить возникновение исключительной ситуации невозможно даже при надлежащем применении API 3048 | - Программист, пользующийся данным API, может предпринять некоторые полезные действия, столкнувшись с этим исключением 3049 | 3050 | Если хоть одно из этих условий не выполняется, следует использовать непроверяемые исключения. 3051 | 3052 | Если метод генерирует единственное проверяемое исключение, то можно рассмотреть возможность использовать `Optional` в 3053 | качестве возвращаемого типа, вместо исключения, если при этом будет достаточно передаваемой информации. 3054 | 3055 | ## 10.4 Предпочитайте использовать стандартные исключения (Item 72) 3056 | 3057 | Часто используемые стандартные исключения: 3058 | 3059 | | Exception | Причина | 3060 | |---------------------------------|-------------------------------------------------------| 3061 | | IllegalArgumentException | Неверное ненулевое значение | 3062 | | IllegalStateException | Неверное состояние объекта для вызова метода | 3063 | | NullPointerException | Неразрешённое нулевое значение параметра | 3064 | | IndexOutOfBoundsException | Индексный параметр за границей допустимого диапазона | 3065 | | ConcurrentModificationException | Обнаружено запрещенное параллельное изменение объекта | 3066 | | UnsupportedOperationException | Объект не поддерживает метод | 3067 | 3068 | Не используйте `Exception`, `RuntimeException`, `Throwable` и `Error` непосредственно. 3069 | 3070 | ## 10.5 Генерируйте исключения, соответствующие абстракции (Item 73) 3071 | 3072 | Трансляция исключений - верхние уровни приложения должны перехватывать исключения нижних уровней и, в свою очередь, 3073 | генерировать исключения, которые можно пояснить в терминах абстракции верхнего уровня. 3074 | 3075 | ```java 3076 | class ExceptionTranslation { 3077 | public static void main(String[] args) { 3078 | // Exception Translation 3079 | try { 3080 | // Use lower-level abstraction to do our bidding 3081 | } catch (LowerLevelException e) { 3082 | throw new HigherLevelException("..."); 3083 | } 3084 | } 3085 | } 3086 | 3087 | ``` 3088 | 3089 | Трансляция цепочкой исключений - исключение нижнего уровня передается исключению верхнего уровня, которое, в свою 3090 | очередь, предоставляет метод доступа(метод `getCause` в `Throwable`), позволяющий получить исключение нижнего уровня. 3091 | 3092 | ```java 3093 | class ExceptionСhaining { 3094 | public static void main(String[] args) { 3095 | // Exception Translation 3096 | try { 3097 | // Use lower-level abstraction to do our bidding 3098 | } catch (LowerLevelException cause) { 3099 | throw new HigherLevelException(cause); 3100 | } 3101 | } 3102 | } 3103 | 3104 | ``` 3105 | 3106 | Трансляция исключения лучше бессмысленной передачи исключений нижнего уровня, злоупотреблять ею не следует. Где это 3107 | возможно, наилучший способ обработки исключений нижнего уровня - полное исключение их возможности путем обеспечения 3108 | гарантированной успешности вызова метода нижнего уроня. 3109 | 3110 | ## 10.6 Документировать все исключения, которые может генерировать метод (Item 74) 3111 | 3112 | Всегда объявляйте проверяемые исключения индивидуально, и точно документируйте условия, при которых каждое из них 3113 | генерируется. 3114 | 3115 | Важна документация непроверяемых исключений, которые могут быть сгенерированы методами интерфейсов, такая документация 3116 | является частью общего контракта. 3117 | 3118 | Используйте дескриптор `Javadoc` `@throws` для описания каждого исключения, которое может быть сгенерировано методом, но 3119 | не используйте ключевое слово `throws` для непроверяемых исключений. 3120 | 3121 | Если одно и то же исключение генерируется несколькими методами класса по одной и то же причине, его можно 3122 | документировать в общей документации всего класса. 3123 | 3124 | ## 10.7 Включайте в сообщения информацию о сбое (Item 75) 3125 | 3126 | Для сохранения информации о сбое, строковое представление исключения должно содержать значения всех параметров и полей, 3127 | приведших к генерации исключения. 3128 | 3129 | Не включайте пароли, ключи шифрования и прочую подобную информацию в детальные сообщения о сбоях. 3130 | 3131 | Чтобы гарантировать, что строковое представление будет содержать нужную информацию, в конструктор помещать информацию о 3132 | сбое, а не строковое представление. Само сообщение будет генерироваться автоматически. 3133 | 3134 | ```java 3135 | public class IndexOutOfBoundsException extends RuntimeException { 3136 | 3137 | public IndexOutOfBoundsException(int lowerBound, int upperBound, int index) { 3138 | // Generate a detail message that captures the failure 3139 | super( 3140 | String.format( 3141 | "Lower bound: %d, Upper bound: %d, Index: %d", lowerBound, upperBound, index)); 3142 | 3143 | // Save failure information for programmatic access 3144 | this.lowerBound = lowerBound; 3145 | this.upperBound = upperBound; 3146 | this.index = index; 3147 | } 3148 | } 3149 | 3150 | ``` 3151 | 3152 | ## 10.8 Добивайтесь атомарности сбоев (Item 76) 3153 | 3154 | Объект атомарный по отношению к сбою - при вызове метода, завершившимся сбоем, должен оставлять проверяемый объект в том 3155 | же состоянии, в каком тот был перед вызовом. 3156 | 3157 | Подходы этого добиться: 3158 | 3159 | - Разработка неизменяемых объектов ([Item 17](#43-минимизируйте-изменяемость-item-17)) 3160 | - Упорядочение вычислений, чтобы все части кода, способные повлечь сбой, предшествовали первой модификации объекта 3161 | - Выполнение операций над временной копией объекта и замене содержимого объекта содержимым копии по завершении операции 3162 | - Использование кода восстановления 3163 | 3164 | Атомарность не всегда желательна, для некоторых операций она существенно увеличивает стоимость или сложность вычисления. 3165 | 3166 | ## 10.9 Не игнорируйте исключения (Item 77) 3167 | 3168 | Когда разработчик API объявляют, что некоторый метод может генерировать исключение, они пытаются этим что-то вам 3169 | сказать. Не игнорируйте это! 3170 | 3171 | Пустой блок `catch` лишает исключение смысла. 3172 | 3173 | Но бывают ситуации, когда игнорирование целесообразно. Тогда блок `catch` должен содержать комментарий, поясняющий, 3174 | почему такое решение приемлемо, а переменная исключения должна иметь имя `ignored`: 3175 | 3176 | ```java 3177 | class ExceptionIgnore { 3178 | void ignore() { 3179 | 3180 | try { 3181 | } catch (TimeoutException | ExecutionException ignored) { 3182 | // Use default: minimal coloring is desirable, not required 3183 | } 3184 | } 3185 | } 3186 | 3187 | ``` 3188 | 3189 | # 11 Многопоточное программирование 3190 | 3191 | ## 11.1 Синхронизируйте доступ к совместно используемым изменяемым данным (Item 78) 3192 | 3193 | Ключевое слово `synchronized` гарантирует, что в любой момент времени метод или блок будет выполняться только 3194 | одним потоком. 3195 | 3196 | Синхронизация не только не дает потоку возможности наблюдать объект в несогласованном состоянии, но также гарантирует, 3197 | что каждый поток, входя в синхронизированный метод или блок, будет видеть результаты выполнения всех предыдущих 3198 | изменений, которые были защищены той же блокировкой. 3199 | 3200 | Java гарантирует, что чтение и запись примитивной переменной атомарна, кроме `long` и `double`. Но атомарные данные тоже 3201 | необходимо синхронизировать, так как нет гарантии, что значение, записанное одним потоком, будет видимо другим. 3202 | 3203 | Синхронизация необходима как для взаимоисключения потоков, так и для надежного взаимодействия между ними. 3204 | 3205 | ```java 3206 | // Broken! - How long would you expect this program to run? 3207 | public class StopThread { 3208 | private static boolean stopRequested; 3209 | 3210 | public static void main(String[] args) { 3211 | Thread backgroundThread = 3212 | new Thread( 3213 | () -> { 3214 | int i = 0; 3215 | while (!stopRequested) i++; 3216 | }); 3217 | backgroundThread.start(); 3218 | TimeUnit.SECONDS.sleep(1); 3219 | stopRequested = true; 3220 | } 3221 | } 3222 | 3223 | ``` 3224 | 3225 | Код выше выполняется бесконечно, в отсвутсвии синхронизации нет гаранитированного времени, когда поток, подлежащий 3226 | остановке, увидит измененное в основном потоке значения `stopRequested`. При отсутсвии сихнонизации JVM может 3227 | преобразовать код: `while(!stopRequested) i++` -> `if(!stopRequested) while(true) i++`. Это называется поднятием. 3228 | 3229 | Решение: 3230 | 3231 | ```java 3232 | // Properly synchronized cooperative thread termination 3233 | public class StopThread { 3234 | private static boolean stopRequested; 3235 | 3236 | private static synchronized void requestStop() { 3237 | stopRequested = true; 3238 | } 3239 | 3240 | private static synchronized boolean stopRequested() { 3241 | return stopRequested; 3242 | } 3243 | 3244 | public static void main(String[] args) { 3245 | Thread backgroundThread = 3246 | new Thread( 3247 | () -> { 3248 | int i = 0; 3249 | while (!stopRequested) i++; 3250 | }); 3251 | backgroundThread.start(); 3252 | 3253 | TimeUnit.SECONDS.sleep(1); 3254 | requestStop(); 3255 | } 3256 | } 3257 | 3258 | ``` 3259 | 3260 | Работа синхронизации не гарантируется, если не синхронизированы обе операции - и чтения, и записи. 3261 | 3262 | Менее многословная альтернатива с более высокой производительности с использованием ключевого слова `volatile`. 3263 | 3264 | ```java 3265 | public class StopThread { 3266 | private static volatile boolean stopRequested; 3267 | 3268 | public static void main(String[] args) { 3269 | Thread backgroundThread = 3270 | new Thread( 3271 | () -> { 3272 | int i = 0; 3273 | while (!stopRequested) i++; 3274 | }); 3275 | backgroundThread.start(); 3276 | 3277 | TimeUnit.SECONDS.sleep(1); 3278 | stopRequested = true; 3279 | } 3280 | } 3281 | 3282 | ``` 3283 | 3284 | Модификатор `volatile` не обеспечивает взаимоисключения, он гарантирует, что любой поток, который читает поле, увидит 3285 | последнее записанное в поле значение. 3286 | 3287 | Использовать поле `volatile` нужно с осторожностью, в случае не атомарной операции над объектом: 3288 | 3289 | ```java 3290 | public class SerialNumber { 3291 | private static volatile int nextSerialNumber = 0; 3292 | 3293 | public static int generateSerialNumber() { 3294 | return nextSerialNumber++; 3295 | } 3296 | } 3297 | 3298 | ``` 3299 | 3300 | Оператор инкремента(`++`) не атомарный. И разные потоки могут получать значение переменной, до того как выполнится 3301 | инкрементация. Что бы метод обрабатывался корректно нужно использовать ключивое слово `synchronized`. 3302 | 3303 | Лучше последовать совету из [Item 59](#93-изучите-и-используйте-возможности-библиотек-item-59) и использовать 3304 | библиотеку `java.util.concurrent.atomic` и класс `AtromicLong`, он представляет примитив безопасный с точки зрения 3305 | многопоточности: 3306 | 3307 | ```java 3308 | public class SerialNumber { 3309 | private static final AtomicLong nextSerialNum = new AtomicLong(); 3310 | 3311 | public static long generateSerialNumber() { 3312 | return nextSerialNum.getAndIncrement(); 3313 | } 3314 | } 3315 | 3316 | ``` 3317 | 3318 | Фактически неизменяемые(effectively immutable) объекты - у которого часть данных может изменить один поток, и 3319 | синхронизировать только изменяемые данные. При этом доступ к другим незаменяемым данным можно выполнять без 3320 | синхронизации. 3321 | 3322 | Наилучший способ избежать вышеописанных проблем - не использовать совместно изменяемые данные. Ограничивайте изменяемые 3323 | данные одним потоком. 3324 | 3325 | ## 11.2 Избегайте излишней синхронизации (Item 79) 3326 | 3327 | Для исключения проблем с живучестью и безопасностью никогда не передавайте управление клиенту из синхронизированного 3328 | метода или блока. То есть из синхронизированного области не следует вызывать методы, которые предназначены для 3329 | переопределения, или метод, предоставленный клиентом в форме функционального 3330 | объекта ([Item 42](#71-предпочитайте-лямбда-выражения-анонимным-классам-item-42)) - чужие методы. 3331 | 3332 | ```java 3333 | public class ObservableSet extends ForwardingSet { 3334 | 3335 | private final List> observers = new ArrayList<>(); 3336 | 3337 | private void notifyElementAdded(E element) { 3338 | // Broken - invokes alien method from synchronized block! 3339 | synchronized (observers) { 3340 | for (SetObserver observer : observers) observer.added(this, element); 3341 | } 3342 | } 3343 | 3344 | @Override 3345 | public boolean add(E element) { 3346 | boolean added = super.add(element); 3347 | if (added) notifyElementAdded(element); 3348 | return added; 3349 | } 3350 | } 3351 | 3352 | ``` 3353 | 3354 | Что бы избежать связанных с этим проблем, можно переместить вызов чужого метода за пределы синхронизированных блоков и 3355 | получать "снимок" объекта с которым можно безопасно работать. 3356 | 3357 | ```java 3358 | public class ObservableSet extends ForwardingSet { 3359 | 3360 | // Alien method moved outside of synchronized block - open calls 3361 | private void notifyElementAdded(E element) { 3362 | List> snapshot = null; 3363 | synchronized (observers) { 3364 | snapshot = new ArrayList<>(observers); 3365 | } 3366 | for (SetObserver observer : snapshot) observer.added(this, element); 3367 | } 3368 | } 3369 | 3370 | 3371 | ``` 3372 | 3373 | Либо использовать библиотеку предоставляющая параллельную 3374 | коллекцию ([Item 81](#114-предпочитайте-утилиты-параллельности-методам-wait-и-notify-item-81)) `CopyOnWriteArraysList`. 3375 | В которой все модифицирующие операции реализуется с помощью создания копии базового массива. Внутренний массив никогда 3376 | не изменяется и итерации не требуют блокировки и выполняются очень быстро. 3377 | 3378 | ```java 3379 | public class ObservableSet extends ForwardingSet { 3380 | 3381 | // Thread-safe observable set with CopyOnWriteArrayList 3382 | private final List> observers = new CopyOnWriteArrayList<>(); 3383 | 3384 | private void notifyElementAdded(E element) { 3385 | for (SetObserver observer : observers) observer.added(this, element); 3386 | } 3387 | } 3388 | 3389 | ``` 3390 | 3391 | Нужно выполнять как можно меньше действий внутри синхронизированной области. 3392 | 3393 | В мультипроцессорном мире реальная стоимость излишней синхронизации - это не время, затрачиваемое процессором на захват 3394 | блокировки, а утерянные возможности параллельного выполнения и задержки, вызванные необходимостью гарантировать, что 3395 | каждое ядро имеет согласованное представление памяти. 3396 | 3397 | Для синхронизации объекта изменяемого класса есть две стратегии: 3398 | 3399 | 1. Разрешить клиенту выполнять внешнюю синхронизацию 3400 | 2. Сделать синхронизацию внутреннюю, то есть сделать объект 3401 | потокобезопасным ([Item 82](#115-документируйте-безопасность-с-точки-зрения-потоков-item-82)) 3402 | 3403 | Если вы сомневаетесь, не синхронизируйте свой класс, но обязательно документируйте, что он не потокобезопасный. 3404 | 3405 | Если несколько потоков могут вызвать метод, который изменяет статическое поле, нужно синхронизировать доступ к полю 3406 | внутренне. 3407 | 3408 | ## 11.3 Предпочитайте исполнителей, задания и потоки данных потокам исполнения (Item 80) 3409 | 3410 | Для создания очереди задач, которые выполняются в многопоточном режиме, лучше использовать `Executor Framework` из 3411 | библиотеки `java.util.concurrent` 3412 | 3413 | Например, фабрика с единственным потоком: 3414 | 3415 | `ExecutorService executor = Executors.newSingleThreadExecutor();` 3416 | 3417 | Передать очереди задания для выполнения: 3418 | 3419 | `executor.execute(runnable);` 3420 | 3421 | Корректно завершить работу очереди: 3422 | 3423 | `executor.shutdown();` 3424 | 3425 | Возможности `Executor Framework`: 3426 | 3427 | - подождать, пока не завершиться определенная задача: `get` 3428 | - подождать, пока одна или все задачи не завершаться: `invokeAny` и `invokeAll` 3429 | - подождать, пока служба не завершиться: `awaitTermination` 3430 | - поочередно выводить результаты выполненных задач по мере завершения: `ExecutorCompletionService` 3431 | - обрабатывать запросы с разным количеством потоков: разные фабрики класса `java.util.concurrent.Executor` 3432 | - управлять почти всеми аспектами работы пула потоков: `ThreadPoolExecutor` 3433 | 3434 | Для небольших программ или мало нагруженных серверов можно выбрать фабрику `Executors.newCachedThreadPool`. Для 3435 | высоконагруженных `Executors.newFixedThreadPool`. 3436 | 3437 | Следует воздержаться от написания собственных рабочих очередей и от непосредственной работы с потоками. Если работать 3438 | непосредственно с классом `Thread`, он служит в качестве единицы работы, так и в качестве механизма её выполнения. 3439 | 3440 | В `Executor Framework` единица работы и выполнения разделены. 3441 | Абстракция единицы работы является - задание. Их виды: 3442 | 3443 | - `Runnable` 3444 | - `Callable`(так же как и `Runnable`, но ещё возвращает результат) 3445 | 3446 | Общий механизм выполнения называется службой исполнителя(executors service). 3447 | 3448 | ## 11.4 Предпочитайте утилиты параллельности методам wait и notify (Item 81) 3449 | 3450 | Начиная c Java 5 появились высокоуровневые утилиты параллельности, которые делают то, что раньше приходилось писать 3451 | вручную поверх `wait` и `notify`. 3452 | 3453 | Высокоуровневые утилиты из `java.util.concurrent`: 3454 | 3455 | - `Executor Framework` ([Item 80](#113-предпочитайте-исполнителей-задания-и-потоки-данных-потокам-исполнения-item-80)) 3456 | - Потокобезопасные коллекции 3457 | - Синхронизаторы 3458 | 3459 | Потокобезопасные коллекции, являются потокобезопасной реализацией стандартных интерфейсов коллекций: `List`, `Queue`, 3460 | и `Map`. Они сами внутренне управляют своей синхронизацией. Исключить многопоточную работу из потокобезопасной коллекции 3461 | невозможно, её общая блокировка будет только тормозить выполнение программы. 3462 | 3463 | Не используйте синхронизируемые коллекции. Например, используйте `ConcurrentHashMap` 3464 | вместо `Collections.synchronizedMap`. 3465 | 3466 | Синхронизаторы предоставляют собой объекты, которые дают возможность потокам ожидать один другого, позволяя тем самым 3467 | координировать их деятельность(`CountDownLatch,` `Semaphore`, `Phaser`), вместо операций `wait` и `notify`. 3468 | 3469 | Если используется устарелый код с `wait` и `notify`. То для `wait` необходимо использовать стандартную идиому: 3470 | 3471 | ```java 3472 | class IdiomWait { 3473 | void idiom() { 3474 | // The standard idiom for using the wait method 3475 | synchronized (obj) { 3476 | while () 3477 | obj.wait(); // (Releases lock, and reacquires on wakeup) 3478 | } 3479 | // Perform action appropriate to condition 3480 | } 3481 | } 3482 | 3483 | ``` 3484 | 3485 | Всегда используйте идиому цикла ожидания для вызова метода `wait`; никогда не вызывайте его вне цикла. 3486 | 3487 | Причины просыпания потока при невыполненном условии: 3488 | 3489 | - Другой поток может получить блокировку и изменить защищенное состояние, когда поток вызвал `notify`. 3490 | - Другой поток может вызвать `notify` случайно или злоумышленно при несоблюдении условия. 3491 | - Уведомляющий поток может быть слишком "щедрым" на пробуждение ожидающих потоков. 3492 | - Ожидающий поток может проснуться при отсутствии уведомления(ложное пробуждения) 3493 | 3494 | В основном следует использовать метод `notifyAll`, `notify` можно использовать в качестве оптимизации, если достаточно 3495 | разбудить один поток из множества. 3496 | 3497 | Причина использовать `wait` и `notify` в новом коде встречается редко(если встречается вообще). 3498 | 3499 | ## 11.5 Документируйте безопасность с точки зрения потоков (Item 82) 3500 | 3501 | Для каждого класса необходимо четко документировать свойства безопасности с точки зрения многопоточности с помощью 3502 | аккуратно составленного текстового описания или аннотации многопоточной безопасности. 3503 | 3504 | Наличие в объявлении метода модификатора `synchronized` - это деталь реализации, а не часть API. Наличие модификатора не 3505 | является надежной гарантией того, что метод безопасен с точки зрения потоков. 3506 | 3507 | Чтобы класс можно было безопасно использовать в многопоточной среде, в документации к нему должно быть четко указано, 3508 | какой уровень безопасности он поддерживает. 3509 | 3510 | Уровни многопоточной безопасности: 3511 | 3512 | - **Неизменяемый** (immutable). Для констант внешней синхронизации не требуется. `String`, `Long` и `BigInteger` и др. 3513 | - **Безусловная безопасность с точки зрения потоков** (unconditionally thread-safe). Могут быть изменяемы, но при этом 3514 | потокобезопасны. `AtomicLong`, `ConcurrentHashMap` и др. 3515 | - **Условная безопасность с точки зрения потоков** (conditionally thread-safe). Потокобезопасный при определенных 3516 | условиях, описанных в документации. Коллекции `Collections.synchronized`. 3517 | - **Небезопасны с точки зрения потоков** (not thread-safe). Для многопоточного использования клиенту нужно окружить 3518 | каждый вызов метода внешней синхронизацией. `ArraysList`, `HashMap` и др. 3519 | - **Несовместимы с многопоточностью** (thread-hostile). Такие классы не пишут целенаправленно; обычно это результат 3520 | неспособности понять параллелизм. 3521 | 3522 | Эти категории примерно соответствуют аннотациям потокобезопасности: `@Immutable`, `@ThreadSafe` и `@NotThreadSafe`. 3523 | 3524 | При документировании класса с условной безопасностью, необходимо указать, какие последовательности вызовов требуют 3525 | внешней синхронизации и какую блокировку необходимо захватывать для выполнения этих последовательностей. 3526 | 3527 | Для класса с безусловной безопасностью, нужно рассмотреть использования закрытого объекта блокировки (идиома закрытого 3528 | объекта блокировки) вместо синхронизированных методов. Это защитит от вмешательства синхронизацию со стороны клиентов 3529 | и подклассов, и обеспечит гибкость в применении более сложных подходов к управлению параллельностью в последующих 3530 | версиях. 3531 | 3532 | ```java 3533 | class LockObjectIdiom { 3534 | // Private lock object idiom - thwarts denial-of-service attack 3535 | private final Object lock = new Object(); 3536 | 3537 | public void foo() { 3538 | synchronized (lock) { 3539 | // ... 3540 | } 3541 | } 3542 | } 3543 | 3544 | ``` 3545 | 3546 | ## 11.6 Аккуратно применяйте отложенную инициализацию (Item 83) 3547 | 3548 | Отложенная (ленивая) инициализация представляет собой задержку инициализации поля до тех пор, пока его значение не 3549 | потребуется для вычислений. Если полю обращается только часть экземпляров и инициализация поля требует существенных 3550 | затрат, то, возможно, стоит использовать отложенную инициализацию. 3551 | 3552 | В большинстве случаев обычная инициализация предпочтительнее отложенной: 3553 | 3554 | ```java 3555 | // Normal initialization of an instance field 3556 | private final FieldType field = computeFieldValue(); 3557 | ``` 3558 | 3559 | При отложенной инициализации, чтобы избежать зацикленность инициализации, необходимо использовать синхронизированный 3560 | метод доступа: 3561 | 3562 | ```java 3563 | public class Initialization { 3564 | // Lazy initialization of instance field - synchronized accessor 3565 | private FieldType field; 3566 | 3567 | private synchronized FieldType getField() { 3568 | if (field == null) field = computeFieldValue(); 3569 | return field; 3570 | } 3571 | } 3572 | 3573 | ``` 3574 | 3575 | Отложенная инициализация статического поля с использованием идиомы класса отложенной инициализации (для повышения 3576 | производительности): 3577 | 3578 | ```java 3579 | public class Initialization { 3580 | // Lazy initialization holder class idiom for static fields 3581 | private static class FieldHolder { 3582 | static final FieldType field = computeFieldValue(); 3583 | } 3584 | 3585 | static FieldType getField() { 3586 | return FieldHolder.field; 3587 | } 3588 | } 3589 | 3590 | ``` 3591 | 3592 | Отложенная инициализация поля экземпляра для повышения производительности с идиомой двойной проверки: 3593 | 3594 | ```java 3595 | public class Initialization { 3596 | // Double-check idiom for lazy initialization of instance fields 3597 | private volatile FieldType field; 3598 | 3599 | FieldType getField() { 3600 | FieldType result = field; 3601 | if (result == null) { // First check (no locking) 3602 | synchronized (this) { 3603 | result = field; 3604 | if (result == null) // Second check (with locking) 3605 | field = result = computeFieldValue(); 3606 | } 3607 | } 3608 | return result; 3609 | } 3610 | } 3611 | 3612 | ``` 3613 | 3614 | Локальная переменная `result` нужна для гарантии однократного чтения `field` в распространенном случае, когда она уже 3615 | инициализирована. Хотя в ней нет острой необходимости, она может улучшить производительность(примерно на 25%) и повысить 3616 | элегантность с точки зрения стандартов низкоуровневого параллельного программирования. 3617 | 3618 | Если поле экземпляра допускает повторную инициализацию, можно использовать идиому однократной проверки: 3619 | 3620 | ```java 3621 | public class Initialization { 3622 | // Single-check idiom - can cause repeated initialization! 3623 | private volatile FieldType field; 3624 | 3625 | private FieldType getField() { 3626 | FieldType result = field; 3627 | if (result == null) field = result = computeFieldValue(); 3628 | return result; 3629 | } 3630 | } 3631 | ``` 3632 | 3633 | ## 11.7 Избегайте зависимости от планировщика потоков (Item 84) 3634 | 3635 | Любая программа, корректность или производительность которой зависит от планировщика потоков, скорее всего, переносимой 3636 | не будет. 3637 | 3638 | Лучший способ написать надежную, с малым временем отклика, переносимую многопоточную программу состоит в том, чтобы в 3639 | любой момент времени среднее количество работающих потоков не было значительно больше количества процессоров. Потоки 3640 | не должны запускаться, если они не выполняют полезную работу. 3641 | 3642 | Потоки не должны быть в активном ожидании с регулярной проверкой состояния совместно используемого объекта в ожидании 3643 | изменения этого состояния. 3644 | 3645 | Приоритеты работы потоков находится среди наименее переносимых возможностей Java. 3646 | 3647 | # 12 Сериализация 3648 | 3649 | ## 12.1 Предпочитайте альтернативы сериализации Java (Item 85) 3650 | 3651 | Фундаментальная проблема сериалзиации состоит в том, что область, в которой она может быть атакована (проблемы с 3652 | безопасностью) слишком велика для защиты и постоянно растет: графы объектов десериализуются с помощью вызовов метода 3653 | `readObject` над `ObjectInputStream`. Сериализация опасна, и её следует избегать. 3654 | 3655 | Лучший способ избежать проблем, связанных с сериализацией, — никогда ничего не десериализовать. Нет никаких оснований 3656 | для использования сериалзиации Java в любой новой системе, которую вы пишите. 3657 | 3658 | Лучше использовать механизмы преобразования между объектами и последовательность байтов, у которых могут быть 3659 | достоинства, такие как эти: 3660 | 3661 | - кроссплатформенность 3662 | - высокая производительность 3663 | - больше инструментов 3664 | - опыт большого сообщества программистов 3665 | - гораздо проще сериализации 3666 | 3667 | Например, JSON и Protocol Buffers. 3668 | 3669 | Если нельзя избежать сериализации, то никогда не десериализуйте непроверенные данные. Для этого можно использовать 3670 | фильтр десериализуемых объектов `java.io.ObjectInputFilter` с черными и белыми списками классов. 3671 | 3672 | ## 12.2 Реализуйте интерфейс Serializable крайне осторожно (Item 86) 3673 | 3674 | Что бы сделать класс сериализуемым достаточно добавить в объявлении метода `implements Serializable`. Но сериализация 3675 | требует от программиста гораздо больше усилий и требует учитывать следующие особенности: 3676 | 3677 | - Уменьшает возможности изменения реализации класса в последующих версиях 3678 | - Повышается вероятность появления ошибок и брешей в системе 3679 | безопасности ([Item 85](#121-предпочитайте-альтернативы-сериализации-java-item-85)) 3680 | - Выпуск новой версии класса сопряжен с большой работой по тестированию 3681 | 3682 | Классы, предназначенные для 3683 | наследования ([Item 19](#45-проектируйте-и-документируйте-наследование-либо-запрещайте-его-item-19)), редко должны 3684 | реализовывать `Serializable`, а интерфейсы - редко его расширять. 3685 | 3686 | Внутренние классы ([Item 24](#410-предпочитайте-вложенный-статический-класс-вложенному-внутреннему-классу-item-24)) не 3687 | должны реализовывать `Serializable`. 3688 | 3689 | ## 12.3 Подумайте о применении пользовательской сериализованной формы (Item 87) 3690 | 3691 | Нельзя принимать сериализованную форму, предлагаемую по умолчанию, не обдумав как следует, насколько она устраивает. 3692 | Можно принимать сериалзиованную форму по умолчанию только тогда, когда она практически идентична той кодировке, которую 3693 | вы бы выбрали, если бы проектировали серилизованную форму сами. Чаще всего бывает, когда физическое представление 3694 | объекта идентично его логическому содержанию (например, класс имени или точки в системе координат). 3695 | 3696 | Даже если вы решите, что сериалзиованная форма по умолчанию для вас пригодна, имейте в виду, что зачастую для 3697 | обеспечения инвариантов и безопасности требуется предоставление 3698 | метода `readObject` ([Item 88](#124-создавайте-защищенные-методы-readobject-item-88)). 3699 | 3700 | Сериализованная форма по умолчанию: 3701 | 3702 | ```java 3703 | public final class StringList implements Serializable { 3704 | 3705 | private transient int size = 0; 3706 | private transient Entry head = null; 3707 | 3708 | private static class Entry implements Serializable { 3709 | String data; 3710 | Entry next; 3711 | Entry previous; 3712 | } 3713 | } 3714 | 3715 | ``` 3716 | 3717 | Применение сериализованной формы по умолчанию, когда физическое представление объекта существенно отличается от 3718 | содержащихся в нем логических данных, имеет следующие недостатки: 3719 | 3720 | - Она навсегда связывает внешний API класса с его текущим внутренним представлением 3721 | - Она может потреблять слишком много памяти 3722 | - Она может требовать слишком много времени 3723 | - Она может вызвать переполнение стека 3724 | 3725 | Правильная сериалзиованная форма: 3726 | 3727 | ```java 3728 | public final class StringList implements Serializable { 3729 | 3730 | private transient int size = 0; 3731 | private transient Entry head = null; 3732 | 3733 | // No longer Serializable! 3734 | private static class Entry { 3735 | String data; 3736 | Entry next; 3737 | Entry previous; 3738 | } 3739 | 3740 | /** 3741 | * Serialize this {@code StringList} instance. 3742 | * 3743 | * @serialData The size of the list (the number of strings it contains) is emitted ({@code int}), 3744 | * followed by all of its elements (each a {@code String}), in the proper sequence. 3745 | */ 3746 | private void writeObject(ObjectOutputStream s) throws IOException { 3747 | s.defaultWriteObject(); 3748 | s.writeInt(size); 3749 | 3750 | // Write out all elements in the proper order. 3751 | for (Entry e = head; e != null; e = e.next) s.writeObject(e.data); 3752 | } 3753 | 3754 | private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException { 3755 | s.defaultReadObject(); 3756 | int numElements = s.readInt(); 3757 | 3758 | // Read in all elements and insert them in list 3759 | for (int i = 0; i < numElements; i++) add((String) s.readObject()); 3760 | } 3761 | } 3762 | 3763 | ``` 3764 | 3765 | Изменения: 3766 | 3767 | - Все поля сериализованного класса помечены как `transient`. Каждое поле, которое не требуется помещать в 3768 | сериализованную форму, следует пометить этим модификатором. Прежде чем решить сделать какое-то поле не 3769 | являющимся `transient`, убедитесь в том, что его значение является частью логического состояния объекта 3770 | - Методы `defaultWriteObject` и `defaultReadObject` согласно спецификации вызываются в любом случае. Это необходимо для 3771 | прямой и обратной совместимости в следующих версиях класса 3772 | - Методы `writeObject` и `readObject` с модификатором доступа `private` документированы, они определяют сериализованную 3773 | форму и являются открытым API 3774 | - Производительность исправленной сериализованной формы быстрее в 2 раза и в 2 раза меньше занимает памяти 3775 | 3776 | Нужно использовать для сериализации объекта ту же синхронизацию, что и для любого иного метода чтения всего состояния 3777 | объекта. 3778 | 3779 | ```java 3780 | class Sync { 3781 | // writeObject for synchronized class with default serialized form 3782 | private synchronized void writeObject(ObjectOutputStream s) throws IOException { 3783 | s.defaultWriteObject(); 3784 | } 3785 | } 3786 | 3787 | ``` 3788 | 3789 | Независимо от того, какую сериализованную форму вы выберете, в каждом сериализуемом классе, который вы пишите, явным 3790 | образом объявляйте идентификатор версии сериализации. Это исключает его из числа потенциальных источников 3791 | несовместимости. 3792 | 3793 | `private static final long serialVersionUID = randomLongValue;` 3794 | 3795 | Не изменяйте идентификатор версии, если только вы не хотите нарушить совместимость со всеми существующими 3796 | сериализованными экземпляра класса. 3797 | 3798 | ## 12.4 Создавайте защищенные методы readObject (Item 88) 3799 | 3800 | Метод `readObject` фактически является открытым конструктором, который приминает в качестве входных параметров поток 3801 | байтов. 3802 | 3803 | Рекомендации по написанию `readObject` (включают рекомендации по созданию открытого конструктора): 3804 | 3805 | - Для классов с полями, в которых хранятся ссылки на объекты и должны оставаться закрытыми, выполняйте защитное 3806 | копирование каждого объекта в таком поле. Изменяемые компоненты неизменяемых классов также попадают в эту 3807 | категорию 3808 | - Выполняйте проверку инвариантов и в случае ошибки генерируйте исключение `InvalidObjectException`. Проверки должны 3809 | производиться после защитного копирования ([Item 50](#82-при-необходимости-создавайте-защитные-копии-item-50)) 3810 | - Если после десериализации необходимо проверить весь граф объектов, используйте интерфейс `ObjectInputValidation` 3811 | - Ни прямо, ни косвенно не вызывайте перекрываемые методы 3812 | класса ([Item 19](#45-проектируйте-и-документируйте-наследование-либо-запрещайте-его-item-19)) 3813 | 3814 | ```java 3815 | class Period { 3816 | // readObject method with defensive copying and validity checking 3817 | private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException { 3818 | s.defaultReadObject(); 3819 | 3820 | // Defensively copy our mutable components 3821 | start = new Date(start.getTime()); 3822 | end = new Date(end.getTime()); 3823 | 3824 | // Check that our invariants are satisfied 3825 | if (start.compareTo(end) > 0) throw new InvalidObjectException(start + " after " + end); 3826 | } 3827 | } 3828 | 3829 | ``` 3830 | 3831 | Альтернатива `readObject` - проектный шаблон прокси 3832 | сериализации ([Item 90](#126-подумайте-о-применении-прокси-агента-сериализации-вместо-сериализованных-экземпляров-item-90)). 3833 | 3834 | ## 12.5 Для управления экземпляром предпочитайте типы перечислений методу readResolve (Item 89) 3835 | 3836 | Чтобы обеспечить управление экземплярами, необходимо использовать `enum` для сериализуемого класса. Например 3837 | для синглтон ([Item 3](#23-получайте-синглтон-с-помощью-закрытого-конструктора-или-типа-перечисления-item-3)). Класс 3838 | перестает быть синглтоном, если в его объявление добавить слова `implements Serializable`. 3839 | 3840 | Если применение `enum` невозможно, например, сериализуемый класс с управляемыми экземплярами, которые в момент 3841 | компиляции неизвестны. Метод `readResolve` позволяет заменить один экземпляр другим, созданным с помощью 3842 | метода `readObject`. С помощью этого метода можно управлять экземплярами класса. Если полагаетесь на метод `readResolve` 3843 | для управления экземпляром, все поля со ссылками на объект должны быть объявлены как `transient`. 3844 | 3845 | Доступность метода `readResolve` имеет важное значение: 3846 | 3847 | * `readResolve` в `final` классе должен быть `private` 3848 | * `readResolve` в не `final` классе 3849 | * `private` - его действия не будут распросторонятья на подклассы 3850 | * по-умолчанию - распространяется на подклассы в пакете 3851 | * `protected` и `public` - распространяется на все подклассы, если подклассы не переопределяют этот метод, то 3852 | десериализация подкласса создаст экземпляр суперкласса, который может вызвать `ClassCastException` 3853 | 3854 | ## 12.6 Подумайте о применении прокси-агента сериализации вместо сериализованных экземпляров (Item 90) 3855 | 3856 | Если требует написать класс, который поддерживает механизм сериализации с помощью пользовательской сериализованной 3857 | формы. Рассмотрите применение шаблона прокси сериализации: 3858 | 3859 | ```java 3860 | // Immutable class that uses defensive copying 3861 | public final class Period implements Serializable { 3862 | private final Date start; 3863 | private final Date end; 3864 | 3865 | /** 3866 | * @param start the beginning of the period 3867 | * @param end the end of the period; must not precede start 3868 | * @throws IllegalArgumentException if start is after end 3869 | * @throws NullPointerException if start or end is null 3870 | */ 3871 | public Period(Date start, Date end) { 3872 | this.start = new Date(start.getTime()); 3873 | this.end = new Date(end.getTime()); 3874 | if (this.start.compareTo(this.end) > 0) 3875 | throw new IllegalArgumentException(start + " after " + end); 3876 | } 3877 | 3878 | public Date start() { 3879 | return new Date(start.getTime()); 3880 | } 3881 | 3882 | public Date end() { 3883 | return new Date(end.getTime()); 3884 | } 3885 | 3886 | public String toString() { 3887 | return start + " - " + end; 3888 | } 3889 | 3890 | // Serialization proxy for Period class 3891 | private static class SerializationProxy implements Serializable { 3892 | private final Date start; 3893 | private final Date end; 3894 | 3895 | SerializationProxy(Period p) { 3896 | this.start = p.start; 3897 | this.end = p.end; 3898 | } 3899 | 3900 | private static final long serialVersionUID = 3901 | 234098243823485285L; // Any number will do (Item 87) 3902 | 3903 | private Object readResolve() { 3904 | return new Period(start, end); 3905 | } 3906 | } 3907 | 3908 | // writeReplace method for the serialization proxy pattern 3909 | private Object writeReplace() { 3910 | return new SerializationProxy(this); 3911 | } 3912 | 3913 | // readObject method for the serialization proxy pattern 3914 | private void readObject(ObjectInputStream stream) throws InvalidObjectException { 3915 | throw new InvalidObjectException("Proxy required"); 3916 | } 3917 | 3918 | 3919 | } 3920 | 3921 | ``` 3922 | 3923 | Для этого необходимо добавить класс прокси сериализации внешнего класса: 3924 | 3925 | - Должен быть закрытым `private` 3926 | - Описывать точно логическое состояние внешнего класса 3927 | - Иметь одним конструктором с типом параметром внешнего класса и копировать данные из него 3928 | - Должен помечен как `Serializable` 3929 | - Во внешнем классе должен быть метод `writeReplace` для сериализации прокси в который транслирует данные внешнего 3930 | класса 3931 | - Во внешнем классе должен быть метод `readObject` который генерирует исключение при использовании. Для защиты от 3932 | инвариантов 3933 | - Иметь метод `readResolve` для десерилизации через прокси, который транслирует уже в конечный экземпляр внешнего 3934 | класса 3935 | 3936 | Плюсы данного подхода в отличие от защитного 3937 | копирования ([Item 88](#124-создавайте-защищенные-методы-readobject-item-88)): 3938 | 3939 | - Можно сделать поля `private` и сделать класс неизменяемым ([Item 17](#43-минимизируйте-изменяемость-item-17)) 3940 | - Не нужно думать, какие поля могут быть дискредитированны при атаках 3941 | - Не нужно выполнять явную проверку корректности 3942 | 3943 | Минусы данного подхода: 3944 | 3945 | - Нельзя создавать дочерние 3946 | классы ([Item 19](#45-проектируйте-и-документируйте-наследование-либо-запрещайте-его-item-19)) 3947 | - Не совместим с классами, графы объектов которых содержат цикличность -------------------------------------------------------------------------------- /effective-java.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /lib/lombok-1.18.24-javadoc.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sergei-zachesov/effective-java/0aae9370f94bb3914b504b63f14adb21049b3675/lib/lombok-1.18.24-javadoc.jar -------------------------------------------------------------------------------- /lib/lombok-1.18.24-sources.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sergei-zachesov/effective-java/0aae9370f94bb3914b504b63f14adb21049b3675/lib/lombok-1.18.24-sources.jar -------------------------------------------------------------------------------- /lib/lombok-1.18.24.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sergei-zachesov/effective-java/0aae9370f94bb3914b504b63f14adb21049b3675/lib/lombok-1.18.24.jar -------------------------------------------------------------------------------- /src/com/zachesov/effectivejava/chapter01/item01/StaticFactoryMethod.java: -------------------------------------------------------------------------------- 1 | package com.zachesov.effectivejava.chapter01.item01; 2 | 3 | import java.math.BigInteger; 4 | import java.sql.DriverManager; 5 | import java.util.Collections; 6 | import java.util.EnumSet; 7 | import java.util.Random; 8 | 9 | /** 10 | * @author Zachesov Sergei 11 | * @since 8/14/2022 12 | */ 13 | public class StaticFactoryMethod { 14 | 15 | // Статичный фабричный метод 16 | 17 | public static void main(String[] args) { 18 | 19 | // + 20 | // 1. Имеет имена 21 | new BigInteger(1, 1, new Random()); 22 | BigInteger.probablePrime(1, new Random()); 23 | 24 | // 2. Может не создавать новый при каждом вызове 25 | Boolean.valueOf(true); 26 | 27 | // 3. Могут возвращать объект любого подтипа их возращаемого типа 28 | Collections.emptySortedSet(); 29 | 30 | // 4. Класс возвращаемого объекта может быть варьироваться 31 | EnumSet.of( 32 | Test.TEST, Test.TEST, Test.TEST, Test.TEST, Test.TEST, Test.TEST, Test.TEST, Test.TEST, 33 | Test.TEST, Test.TEST); 34 | 35 | // *5. Класс возвращаемого объекта не обязан существовать во время разработки класса 36 | DriverManager.getDrivers(); 37 | 38 | // - 39 | // 1. Классы без открытых или защищенных конструкторов не могут порождать подклассы 40 | Collections.emptySortedSet(); 41 | 42 | // 2. Трудно отличить от других статистических методов 43 | 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/com/zachesov/effectivejava/chapter01/item01/Test.java: -------------------------------------------------------------------------------- 1 | package com.zachesov.effectivejava.chapter01.item01; 2 | 3 | /** 4 | * @author Zachesov Sergei 5 | * @since 8/15/2022 6 | */ 7 | public enum Test { 8 | TEST 9 | } 10 | -------------------------------------------------------------------------------- /src/com/zachesov/effectivejava/chapter01/item02/builder/NutritionFacts.java: -------------------------------------------------------------------------------- 1 | package com.zachesov.effectivejava.chapter01.item02.builder; 2 | 3 | // При большом количестве параметров конструктора подумайте о шаблоне Строитель 4 | /** 5 | * @author Zachesov Sergei 6 | * @since 8/16/2022 7 | */ 8 | public class NutritionFacts { 9 | 10 | private final int servingSize; 11 | private final int servings; 12 | private final int calories; 13 | private final int fat; 14 | private final int sodium; 15 | private final int carbohydrate; 16 | 17 | public static class Builder { 18 | // Required parameters 19 | private final int servingSize; 20 | private final int servings; 21 | 22 | // Optional parameters - initialized to default values 23 | private int calories = 0; 24 | private int fat = 0; 25 | private int sodium = 0; 26 | private int carbohydrate = 0; 27 | 28 | public Builder(int servingSize, int servings) { 29 | this.servingSize = servingSize; 30 | this.servings = servings; 31 | } 32 | 33 | public Builder calories(int val) { 34 | calories = val; 35 | return this; 36 | } 37 | 38 | public Builder fat(int val) { 39 | fat = val; 40 | return this; 41 | } 42 | 43 | public Builder sodium(int val) { 44 | sodium = val; 45 | return this; 46 | } 47 | 48 | public Builder carbohydrate(int val) { 49 | carbohydrate = val; 50 | return this; 51 | } 52 | 53 | public NutritionFacts build() { 54 | return new NutritionFacts(this); 55 | } 56 | } 57 | 58 | private NutritionFacts(Builder builder) { 59 | servingSize = builder.servingSize; 60 | servings = builder.servings; 61 | calories = builder.calories; 62 | fat = builder.fat; 63 | sodium = builder.sodium; 64 | carbohydrate = builder.carbohydrate; 65 | } 66 | 67 | public static void main(String[] args) { 68 | NutritionFacts cocaCola = 69 | new NutritionFacts.Builder(240, 8).calories(100).sodium(35).carbohydrate(27).build(); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/com/zachesov/effectivejava/chapter01/item02/builder/NutritionFactsLombok.java: -------------------------------------------------------------------------------- 1 | package com.zachesov.effectivejava.chapter01.item02.builder; 2 | 3 | import lombok.AccessLevel; 4 | import lombok.Builder; 5 | import lombok.ToString; 6 | 7 | import java.util.Set; 8 | 9 | // При большом количестве параметров конструктора подумайте о шаблоне Строитель 10 | /** 11 | * @author Zachesov Sergei 12 | * @since 8/16/2022 13 | */ 14 | @Builder(builderMethodName = "hiddenBuilder", access = AccessLevel.PRIVATE) 15 | @ToString 16 | public class NutritionFactsLombok { 17 | 18 | private final int servingSize; 19 | private final int servings; 20 | private final int calories; 21 | private final int fat; 22 | private final int sodium; 23 | private final int carbohydrate; 24 | 25 | public static NutritionFactsLombokBuilder builder(int servingSize, int servings) { 26 | return hiddenBuilder().servingSize(servingSize).servings(servings); 27 | } 28 | 29 | 30 | public static void main(String[] args) { 31 | NutritionFactsLombok cocaCola = 32 | NutritionFactsLombok.builder(240, 8).calories(100).sodium(35).carbohydrate(27).build(); 33 | System.out.println(cocaCola); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/com/zachesov/effectivejava/chapter01/item02/hierarchicalbuilder/Calzone.java: -------------------------------------------------------------------------------- 1 | package com.zachesov.effectivejava.chapter01.item02.hierarchicalbuilder; 2 | 3 | /** 4 | * @author Zachesov Sergei 5 | * @since 8/16/2022 6 | */ 7 | public class Calzone extends Pizza { 8 | 9 | private final boolean sauceInside; 10 | 11 | public static class Builder extends Pizza.Builder { 12 | private boolean sauceInside = false; // Default 13 | 14 | public Builder sauceInside() { 15 | sauceInside = true; 16 | return this; 17 | } 18 | 19 | @Override 20 | public Calzone build() { 21 | return new Calzone(this); 22 | } 23 | 24 | @Override 25 | protected Builder self() { 26 | return this; 27 | } 28 | } 29 | 30 | private Calzone(Builder builder) { 31 | super(builder); 32 | sauceInside = builder.sauceInside; 33 | } 34 | 35 | @Override 36 | public String toString() { 37 | return String.format( 38 | "Calzone with %s and sauce on the %s", toppings, sauceInside ? "inside" : "outside"); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/com/zachesov/effectivejava/chapter01/item02/hierarchicalbuilder/NyPizza.java: -------------------------------------------------------------------------------- 1 | package com.zachesov.effectivejava.chapter01.item02.hierarchicalbuilder; 2 | 3 | import java.util.Objects; 4 | 5 | /** 6 | * @author Zachesov Sergei 7 | * @since 8/16/2022 8 | */ 9 | public class NyPizza extends Pizza { 10 | public enum Size { 11 | SMALL, 12 | MEDIUM, 13 | LARGE 14 | } 15 | 16 | private final Size size; 17 | 18 | public static class Builder extends Pizza.Builder { 19 | private final Size size; 20 | 21 | public Builder(Size size) { 22 | this.size = Objects.requireNonNull(size); 23 | } 24 | 25 | @Override 26 | public NyPizza build() { 27 | return new NyPizza(this); 28 | } 29 | 30 | @Override 31 | protected Builder self() { 32 | return this; 33 | } 34 | } 35 | 36 | private NyPizza(Builder builder) { 37 | super(builder); 38 | size = builder.size; 39 | } 40 | 41 | @Override 42 | public String toString() { 43 | return "New York Pizza with " + toppings; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/com/zachesov/effectivejava/chapter01/item02/hierarchicalbuilder/Pizza.java: -------------------------------------------------------------------------------- 1 | package com.zachesov.effectivejava.chapter01.item02.hierarchicalbuilder; 2 | 3 | import java.util.EnumSet; 4 | import java.util.Objects; 5 | import java.util.Set; 6 | 7 | /** 8 | * @author Zachesov Sergei 9 | * @since 8/16/2022 10 | */ 11 | public class Pizza { 12 | public enum Topping { 13 | HAM, 14 | MUSHROOM, 15 | ONION, 16 | PEPPER, 17 | SAUSAGE 18 | } 19 | 20 | final Set toppings; 21 | 22 | abstract static class Builder> { 23 | EnumSet toppings = EnumSet.noneOf(Topping.class); 24 | 25 | public T addTopping(Topping topping) { 26 | toppings.add(Objects.requireNonNull(topping)); 27 | return self(); 28 | } 29 | 30 | abstract Pizza build(); 31 | 32 | // Subclasses must override this method to return "this" 33 | protected abstract T self(); 34 | } 35 | 36 | Pizza(Builder builder) { 37 | toppings = builder.toppings.clone(); // See Item 50 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/com/zachesov/effectivejava/chapter01/item02/hierarchicalbuilder/PizzaTest.java: -------------------------------------------------------------------------------- 1 | package com.zachesov.effectivejava.chapter01.item02.hierarchicalbuilder; 2 | 3 | import static com.zachesov.effectivejava.chapter01.item02.hierarchicalbuilder.NyPizza.Size.SMALL; 4 | import static com.zachesov.effectivejava.chapter01.item02.hierarchicalbuilder.Pizza.Topping.*; 5 | 6 | // Строитель для иерархии классов 7 | /** 8 | * @author Zachesov Sergei 9 | * @since 8/16/2022 10 | */ 11 | public class PizzaTest { 12 | public static void main(String[] args) { 13 | NyPizza pizza = new NyPizza.Builder(SMALL).addTopping(SAUSAGE).addTopping(ONION).build(); 14 | Calzone calzone = new Calzone.Builder().addTopping(HAM).sauceInside().build(); 15 | 16 | System.out.println(pizza); 17 | System.out.println(calzone); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/com/zachesov/effectivejava/chapter01/item03/SingletonEnum.java: -------------------------------------------------------------------------------- 1 | package com.zachesov.effectivejava.chapter01.item03; 2 | 3 | /** 4 | * @author Zachesov Sergei 5 | * @since 8/20/2022 6 | */ 7 | public enum SingletonEnum { 8 | INSTANCE; 9 | 10 | public void leaveTheBuilding() { 11 | System.out.println("Whoa baby, I'm outta here!"); 12 | } 13 | 14 | // This code would normally appear outside the class! 15 | public static void main(String[] args) { 16 | SingletonEnum.INSTANCE.leaveTheBuilding(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/com/zachesov/effectivejava/chapter01/item03/SingletonField.java: -------------------------------------------------------------------------------- 1 | package com.zachesov.effectivejava.chapter01.item03; 2 | 3 | /** 4 | * @author Zachesov Sergei 5 | * @since 8/20/2022 6 | */ 7 | public class SingletonField { 8 | 9 | public static final SingletonField INSTANCE = new SingletonField(); 10 | 11 | private SingletonField() {} 12 | 13 | public void leaveTheBuilding() { 14 | System.out.println("Whoa baby, I'm outta here!"); 15 | } 16 | 17 | // This code would normally appear outside the class! 18 | public static void main(String[] args) { 19 | SingletonField.INSTANCE.leaveTheBuilding(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/com/zachesov/effectivejava/chapter01/item03/SingletonStaticFactory.java: -------------------------------------------------------------------------------- 1 | package com.zachesov.effectivejava.chapter01.item03; 2 | 3 | /** 4 | * @author Zachesov Sergei 5 | * @since 8/20/2022 6 | */ 7 | public class SingletonStaticFactory { 8 | 9 | private static final SingletonStaticFactory INSTANCE = new SingletonStaticFactory(); 10 | 11 | private SingletonStaticFactory() {} 12 | 13 | public static SingletonStaticFactory getInstance() { 14 | return INSTANCE; 15 | } 16 | 17 | public void leaveTheBuilding() { 18 | System.out.println("Whoa baby, I'm outta here!"); 19 | } 20 | 21 | // This code would normally appear outside the class! 22 | public static void main(String[] args) { 23 | SingletonStaticFactory singletonStaticFactory = SingletonStaticFactory.getInstance(); 24 | singletonStaticFactory.leaveTheBuilding(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/com/zachesov/effectivejava/chapter01/item04/UtilityClass.java: -------------------------------------------------------------------------------- 1 | package com.zachesov.effectivejava.chapter01.item04; 2 | 3 | /** 4 | * @author Zachesov Sergei 5 | * @since 8/20/2022 6 | */ 7 | public class UtilityClass { 8 | 9 | // Suppress default constructor for noninstantiability 10 | private UtilityClass() { 11 | throw new AssertionError(); 12 | } 13 | 14 | // Remainder omitted 15 | } 16 | -------------------------------------------------------------------------------- /src/com/zachesov/effectivejava/chapter01/item06/RomanNumerals.java: -------------------------------------------------------------------------------- 1 | package com.zachesov.effectivejava.chapter01.item06; 2 | 3 | import java.util.regex.Pattern; 4 | 5 | /** 6 | * @author Zachesov Sergei 7 | * @since 8/21/2022 8 | */ 9 | public class RomanNumerals { 10 | 11 | // Performance can be greatly improved! (Page 22) 12 | static boolean isRomanNumeralSlow(String s) { 13 | return s.matches("^(?=.)M*(C[MD]|D?C{0,3})" + "(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$"); 14 | } 15 | 16 | // Reusing expensive object for improved performance (Page 23) 17 | private static final Pattern ROMAN = 18 | Pattern.compile("^(?=.)M*(C[MD]|D?C{0,3})" + "(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$"); 19 | 20 | static boolean isRomanNumeralFast(String s) { 21 | return ROMAN.matcher(s).matches(); 22 | } 23 | 24 | public static void main(String[] args) { 25 | int numSets = Integer.parseInt(args[0]); 26 | int numReps = Integer.parseInt(args[1]); 27 | boolean b = false; 28 | 29 | for (int i = 0; i < numSets; i++) { 30 | long start = System.nanoTime(); 31 | for (int j = 0; j < numReps; j++) { 32 | b ^= isRomanNumeralSlow("MCMLXXVI"); // Change Slow to Fast to see performance difference 33 | } 34 | long end = System.nanoTime(); 35 | System.out.println(((end - start) / (1_000. * numReps)) + " μs."); 36 | } 37 | 38 | // Prevents VM from optimizing away everything. 39 | if (!b) System.out.println(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/com/zachesov/effectivejava/chapter01/item06/Sum.java: -------------------------------------------------------------------------------- 1 | package com.zachesov.effectivejava.chapter01.item06; 2 | 3 | /** 4 | * @author Zachesov Sergei 5 | * @since 8/21/2022 6 | */ 7 | public class Sum { 8 | 9 | private static long sum() { 10 | Long sum = 0L; 11 | for (long i = 0; i <= Integer.MAX_VALUE; i++) sum += i; 12 | return sum; 13 | } 14 | 15 | public static void main(String[] args) { 16 | int numSets = Integer.parseInt(args[0]); 17 | long x = 0; 18 | 19 | for (int i = 0; i < numSets; i++) { 20 | long start = System.nanoTime(); 21 | x += sum(); 22 | long end = System.nanoTime(); 23 | System.out.println((end - start) / 1_000_000. + " ms."); 24 | } 25 | 26 | // Prevents VM from optimizing away everything. 27 | if (x == 42) System.out.println(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/com/zachesov/effectivejava/chapter01/item07/EmptyStackException.java: -------------------------------------------------------------------------------- 1 | package com.zachesov.effectivejava.chapter01.item07; 2 | 3 | /** 4 | * @author Zachesov Sergei 5 | * @since 8/23/2022 6 | */ 7 | public class EmptyStackException extends IllegalStateException {} 8 | -------------------------------------------------------------------------------- /src/com/zachesov/effectivejava/chapter01/item07/Stack.java: -------------------------------------------------------------------------------- 1 | package com.zachesov.effectivejava.chapter01.item07; 2 | 3 | import java.util.Arrays; 4 | 5 | /** 6 | * @author Zachesov Sergei 7 | * @since 8/23/2022 8 | */ 9 | // Can you spot the "memory leak"? (Pages 26-27) 10 | public class Stack { 11 | private Object[] elements; 12 | private int size = 0; 13 | private static final int DEFAULT_INITIAL_CAPACITY = 16; 14 | 15 | public Stack() { 16 | elements = new Object[DEFAULT_INITIAL_CAPACITY]; 17 | } 18 | 19 | public void push(Object e) { 20 | ensureCapacity(); 21 | elements[size++] = e; 22 | } 23 | 24 | public Object pop() { 25 | if (size == 0) throw new EmptyStackException(); 26 | return elements[--size]; 27 | } 28 | 29 | /** 30 | * Ensure space for at least one more element, roughly doubling the capacity each time the array 31 | * needs to grow. 32 | */ 33 | private void ensureCapacity() { 34 | if (elements.length == size) elements = Arrays.copyOf(elements, 2 * size + 1); 35 | } 36 | 37 | // // Corrected version of pop method (Page 27) 38 | // public Object pop() { 39 | // if (size == 0) 40 | // throw new EmptyStackException(); 41 | // Object result = elements[--size]; 42 | // elements[size] = null; // Eliminate obsolete reference 43 | // return result; 44 | // } 45 | 46 | public static void main(String[] args) { 47 | Stack stack = new Stack(); 48 | for (String arg : args) stack.push(arg); 49 | 50 | while (true) System.err.println(stack.pop()); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/com/zachesov/effectivejava/chapter06/item04/Plant.java: -------------------------------------------------------------------------------- 1 | package com.zachesov.effectivejava.chapter06.item04; 2 | 3 | import java.util.*; 4 | 5 | import static java.util.stream.Collectors.groupingBy; 6 | import static java.util.stream.Collectors.toSet; 7 | 8 | /** 9 | * @author Zachesov Sergei 10 | * @since 11/4/2022 11 | */ 12 | public class Plant { 13 | 14 | enum LifeCycle { 15 | ANNUAL, 16 | PERENNIAL, 17 | BIENNIAL 18 | } 19 | 20 | final String name; 21 | final LifeCycle lifeCycle; 22 | 23 | Plant(String name, LifeCycle lifeCycle) { 24 | this.name = name; 25 | this.lifeCycle = lifeCycle; 26 | } 27 | 28 | @Override 29 | public String toString() { 30 | return name; 31 | } 32 | 33 | public static void main(String[] args) { 34 | Plant[] garden = { 35 | new Plant("Basil", LifeCycle.ANNUAL), 36 | new Plant("Carroway", LifeCycle.BIENNIAL), 37 | new Plant("Dill", LifeCycle.ANNUAL), 38 | new Plant("Lavendar", LifeCycle.PERENNIAL), 39 | new Plant("Parsley", LifeCycle.BIENNIAL), 40 | new Plant("Rosemary", LifeCycle.PERENNIAL) 41 | }; 42 | 43 | // Using ordinal() to index into an array - DON'T DO THIS! (Page 171) 44 | Set[] plantsByLifeCycleArr = (Set[]) new Set[Plant.LifeCycle.values().length]; 45 | for (int i = 0; i < plantsByLifeCycleArr.length; i++) plantsByLifeCycleArr[i] = new HashSet<>(); 46 | for (Plant p : garden) plantsByLifeCycleArr[p.lifeCycle.ordinal()].add(p); 47 | // Print the results 48 | for (int i = 0; i < plantsByLifeCycleArr.length; i++) { 49 | System.out.printf("%s: %s%n", Plant.LifeCycle.values()[i], plantsByLifeCycleArr[i]); 50 | } 51 | 52 | // Using an EnumMap to associate data with an enum (Page 172) 53 | Map> plantsByLifeCycle = new EnumMap<>(Plant.LifeCycle.class); 54 | for (Plant.LifeCycle lc : Plant.LifeCycle.values()) plantsByLifeCycle.put(lc, new HashSet<>()); 55 | for (Plant p : garden) plantsByLifeCycle.get(p.lifeCycle).add(p); 56 | System.out.println(plantsByLifeCycle); 57 | 58 | // Naive stream-based approach - unlikely to produce an EnumMap! (Page 172) 59 | System.out.println(Arrays.stream(garden).collect(groupingBy(p -> p.lifeCycle))); 60 | 61 | // Using a stream and an EnumMap to associate data with an enum (Page 173) 62 | System.out.println( 63 | Arrays.stream(garden) 64 | .collect(groupingBy(p -> p.lifeCycle, () -> new EnumMap<>(LifeCycle.class), toSet()))); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/com/zachesov/effectivejava/chapter07/item05/keytoelement/Album.java: -------------------------------------------------------------------------------- 1 | package com.zachesov.effectivejava.chapter07.item05.keytoelement; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | import lombok.ToString; 6 | 7 | /** 8 | * @author Zachesov Sergei 9 | * @since 11/8/2022 10 | */ 11 | @ToString 12 | @AllArgsConstructor 13 | @Getter 14 | public class Album { 15 | 16 | private String name; 17 | 18 | private double sales; 19 | 20 | private Artist artist; 21 | } 22 | -------------------------------------------------------------------------------- /src/com/zachesov/effectivejava/chapter07/item05/keytoelement/Artist.java: -------------------------------------------------------------------------------- 1 | package com.zachesov.effectivejava.chapter07.item05.keytoelement; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.ToString; 5 | 6 | /** 7 | * @author Zachesov Sergei 8 | * @since 11/8/2022 9 | */ 10 | @ToString 11 | @AllArgsConstructor 12 | public class Artist { 13 | 14 | private String name; 15 | } 16 | -------------------------------------------------------------------------------- /src/com/zachesov/effectivejava/chapter07/item05/keytoelement/Main.java: -------------------------------------------------------------------------------- 1 | package com.zachesov.effectivejava.chapter07.item05.keytoelement; 2 | 3 | import java.util.List; 4 | import java.util.Map; 5 | 6 | import static java.util.Comparator.comparing; 7 | import static java.util.function.BinaryOperator.maxBy; 8 | import static java.util.stream.Collectors.toMap; 9 | 10 | /** 11 | * @author Zachesov Sergei 12 | * @since 11/8/2022 13 | */ 14 | public class Main { 15 | 16 | public static void main(String[] args) { 17 | 18 | Artist artistA = new Artist("Sting"); 19 | Artist artistB = new Artist("Splin"); 20 | 21 | Album albumA = new Album("A", 1, artistA); 22 | Album albumB = new Album("B", 1000, artistB); 23 | Album albumC = new Album("C", 4000, artistB); 24 | Album albumD = new Album("D", 10000, artistA); 25 | Album albumE = new Album("E", 500, artistB); 26 | Album albumF = new Album("F", 1000, artistA); 27 | 28 | List albums = List.of(albumA, albumB, albumC, albumC, albumD, albumE, albumF); 29 | 30 | Map topHits = 31 | albums.stream().collect(toMap(Album::getArtist, a -> a, maxBy(comparing(Album::getSales)))); 32 | System.out.println(topHits); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/com/zachesov/effectivejava/chapter11/item79/ForwardingSet.java: -------------------------------------------------------------------------------- 1 | package com.zachesov.effectivejava.chapter11.item79; 2 | 3 | import java.util.Collection; 4 | import java.util.Iterator; 5 | import java.util.Set; 6 | 7 | /** 8 | * @author Zachesov Sergei 9 | * @since 2023-02-20 10 | */ 11 | public class ForwardingSet implements Set { 12 | 13 | private final Set s; 14 | 15 | public ForwardingSet(Set s) { 16 | this.s = s; 17 | } 18 | 19 | public void clear() { 20 | s.clear(); 21 | } 22 | 23 | public boolean contains(Object o) { 24 | return s.contains(o); 25 | } 26 | 27 | public boolean isEmpty() { 28 | return s.isEmpty(); 29 | } 30 | 31 | public int size() { 32 | return s.size(); 33 | } 34 | 35 | public Iterator iterator() { 36 | return s.iterator(); 37 | } 38 | 39 | public boolean add(E e) { 40 | return s.add(e); 41 | } 42 | 43 | public boolean remove(Object o) { 44 | return s.remove(o); 45 | } 46 | 47 | public boolean containsAll(Collection c) { 48 | return s.containsAll(c); 49 | } 50 | 51 | public boolean addAll(Collection c) { 52 | return s.addAll(c); 53 | } 54 | 55 | public boolean removeAll(Collection c) { 56 | return s.removeAll(c); 57 | } 58 | 59 | public boolean retainAll(Collection c) { 60 | return s.retainAll(c); 61 | } 62 | 63 | public Object[] toArray() { 64 | return s.toArray(); 65 | } 66 | 67 | public T[] toArray(T[] a) { 68 | return s.toArray(a); 69 | } 70 | 71 | @Override 72 | public boolean equals(Object o) { 73 | return s.equals(o); 74 | } 75 | 76 | @Override 77 | public int hashCode() { 78 | return s.hashCode(); 79 | } 80 | 81 | @Override 82 | public String toString() { 83 | return s.toString(); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/com/zachesov/effectivejava/chapter11/item79/ObservableSet.java: -------------------------------------------------------------------------------- 1 | package com.zachesov.effectivejava.chapter11.item79; 2 | 3 | import java.util.Collection; 4 | import java.util.List; 5 | import java.util.Set; 6 | import java.util.concurrent.CopyOnWriteArrayList; 7 | 8 | /** 9 | * @author Zachesov Sergei 10 | * @since 2023-02-20 11 | */ 12 | public class ObservableSet extends ForwardingSet { 13 | 14 | public ObservableSet(Set s) { 15 | super(s); 16 | } 17 | 18 | // private final List> observers = new ArrayList<>(); 19 | // 20 | // public void addObserver(SetObserver observer) { 21 | // synchronized (observers) { 22 | // observers.add(observer); 23 | // } 24 | // } 25 | // 26 | // public boolean removeObserver(SetObserver observer) { 27 | // synchronized (observers) { 28 | // return observers.remove(observer); 29 | // } 30 | 31 | // private void notifyElementAdded(E element) { 32 | // synchronized (observers) { 33 | // for (SetObserver observer : observers) observer.added(this, element); 34 | // } 35 | // } 36 | 37 | // Alien method moved outside of synchronized block - open calls 38 | // private void notifyElementAdded(E element) { 39 | // List> snapshot = null; 40 | // synchronized (observers) { 41 | // snapshot = new ArrayList<>(observers); 42 | // } 43 | // for (SetObserver observer : snapshot) observer.added(this, element); 44 | // } 45 | 46 | // Thread-safe observable set with CopyOnWriteArrayList 47 | private final List> observers = new CopyOnWriteArrayList<>(); 48 | 49 | public void addObserver(SetObserver observer) { 50 | observers.add(observer); 51 | } 52 | 53 | public boolean removeObserver(SetObserver observer) { 54 | return observers.remove(observer); 55 | } 56 | 57 | private void notifyElementAdded(E element) { 58 | for (SetObserver observer : observers) observer.added(this, element); 59 | } 60 | 61 | @Override 62 | public boolean add(E element) { 63 | boolean added = super.add(element); 64 | if (added) notifyElementAdded(element); 65 | return added; 66 | } 67 | 68 | @Override 69 | public boolean addAll(Collection c) { 70 | boolean result = false; 71 | for (E element : c) result |= add(element); // Calls notifyElementAdded 72 | return result; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/com/zachesov/effectivejava/chapter11/item79/SetObserver.java: -------------------------------------------------------------------------------- 1 | package com.zachesov.effectivejava.chapter11.item79; 2 | 3 | /** 4 | * @author Zachesov Sergei 5 | * @since 2023-02-20 6 | */ 7 | @FunctionalInterface 8 | public interface SetObserver { 9 | 10 | void added(ObservableSet set, E element); 11 | } 12 | -------------------------------------------------------------------------------- /src/com/zachesov/effectivejava/chapter11/item79/Test1.java: -------------------------------------------------------------------------------- 1 | package com.zachesov.effectivejava.chapter11.item79; 2 | 3 | import java.util.HashSet; 4 | 5 | /** 6 | * @author Zachesov Sergei 7 | * @since 2023-02-20 8 | */ 9 | public class Test1 { 10 | 11 | public static void main(String[] args) { 12 | ObservableSet set = new ObservableSet<>(new HashSet<>()); 13 | 14 | set.addObserver((s, e) -> System.out.println(e)); 15 | 16 | for (int i = 0; i < 100; i++) set.add(i); 17 | 18 | 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/com/zachesov/effectivejava/chapter11/item79/Test2.java: -------------------------------------------------------------------------------- 1 | package com.zachesov.effectivejava.chapter11.item79; 2 | 3 | import java.util.HashSet; 4 | 5 | /** 6 | * @author Zachesov Sergei 7 | * @since 2023-02-20 8 | */ 9 | public class Test2 { 10 | 11 | public static void main(String[] args) { 12 | ObservableSet set = new ObservableSet<>(new HashSet<>()); 13 | 14 | set.addObserver( 15 | new SetObserver<>() { 16 | public void added(ObservableSet s, Integer e) { 17 | System.out.println(e); 18 | if (e == 23) s.removeObserver(this); 19 | } 20 | }); 21 | 22 | for (int i = 0; i < 100; i++) set.add(i); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/com/zachesov/effectivejava/chapter11/item79/Test3.java: -------------------------------------------------------------------------------- 1 | package com.zachesov.effectivejava.chapter11.item79; 2 | 3 | import java.util.HashSet; 4 | import java.util.concurrent.ExecutionException; 5 | import java.util.concurrent.ExecutorService; 6 | import java.util.concurrent.Executors; 7 | 8 | /** 9 | * @author Zachesov Sergei 10 | * @since 2023-02-20 11 | */ 12 | public class Test3 { 13 | 14 | public static void main(String[] args) { 15 | ObservableSet set = new ObservableSet<>(new HashSet<>()); 16 | 17 | // Observer that uses a background thread needlessly 18 | set.addObserver( 19 | new SetObserver<>() { 20 | public void added(ObservableSet s, Integer e) { 21 | System.out.println(e); 22 | if (e == 23) { 23 | ExecutorService exec = Executors.newSingleThreadExecutor(); 24 | try { 25 | exec.submit(() -> s.removeObserver(this)).get(); 26 | } catch (ExecutionException | InterruptedException ex) { 27 | throw new AssertionError(ex); 28 | } finally { 29 | exec.shutdown(); 30 | } 31 | } 32 | } 33 | }); 34 | 35 | for (int i = 0; i < 100; i++) set.add(i); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/effective-java-3e-source-code: -------------------------------------------------------------------------------- 1 | https://github.com/jbloch/effective-java-3e-source-code/tree/master/src/effectivejava --------------------------------------------------------------------------------