1. Введение
2. Создание и уничтожение объектов
- Замена конструктора статическим фабричным методом
- Полезно в случае:
- Не понятное имя конструктора;
- Возножность создавать конструкторы с одинаковыми параметрами но разными именами;
- Использование созданных членов класса;
- Возможность вернуть тип наследника. Пимер: не модифицируемые Collections;
- Возвращаемый тип может меняться от типа значения параметра;
- Можно улучшать реализацию от версии к версии;
- Код эеземпляра не обязан существовать намемент вызова фабричного метода.
- Недостатки фабричного метода:
- Классы только с статическими фабричными методами не могут порождать подклассы;
- Не всегда понятные имена ФМ. Потому нужно использовать принятое именование (pages: 33-34).
- Шаблон Bilder вместо множества параметров (в том числе пример рекурсивного Generic(pages: 40-41)
- Плюсы:
- Легче расширять;
- Лучше чем подход с JavaBeans (pages: 36-37)
- Удобнее расширять в библиотеке.
- Минусы:
- Время на нписание;
- Громоздкость при малом числе параметров.
- Синглтон
- Рассмотрен вопрос сериализации (pages: 43-45).
- Особенности реализации:
- Как поле класса;
- Метод getInstance();
- Enum с одним значением;
- Передача параметра лучше чем жестко зашитый ресурс (pages: 47-49):
- Поддержка разных версий/языков, а также при тестировании;
- Проблема dependencyInjection;
- Разные классы могут использовать один/разные версии этого класса даже не подозревая об этом;
- Избегайте создания лишних объектов:
- String s = new String("bad");
- long better than Long - не пользуем Bound Boxing где не нужно (яркий пример с циклом pages: 52-53).
- Избегайте устаревших ссылок на объекты:
- Обнулять все подряд не нужно;
- Лучший вариант: ограничение области видимости переменной и автоматическое ее удаление;
- Случай с массивом объектов, которые не используются(удаленные элементы из стека, листа. page: 54);
- Кеш;
- Обратные вызовы - лучше использовать слабые ссылки.
- Финализаторы и очестители:
- Не гарантируются;
- Устаревшие методы: System.runFinalizersOnExit(), Runtime.runFinalizersOnExit();
- Игнор исключений;
- Проблемы с производительностью;
- ДЕЛАЙТЕ КЛАСС AutoCloseable (pages: 60-62);
- Зачем же тогда они нужны?
- Контроль вызова close для подстраховки;
- Удаление платформозавсисемых узлов(С++ и пр.)
- TRY с ресурсами используем вместо try-finaly.
3. Методы общие для всех объектов
- equals()
- условия:
- Рефлексивнсть x.equals(x) = true;
- Симметричность x.equals(y) , y.equals(x);
- Транзитивность x.equals(y), y.equals(z), z.equals(x);
- Непротиворечивость for(0~100000) x.equals(x)=true/false (always);
- x.equals(null) = false.
- Правила написания своего equals(Object o):
- this==o;
- if(!(o instanceof MyType)) return fasle;
- Сравниваем значемые поля (начиная с более простых и более изменяемых);
- hashCode!!!!! нужно перекрыть при перекрытии equals (pages: 81-87).
- Правила написания своего hash:
- Для примитивных полей Type.hashCode(f);
- Сложные типы: поступайте как поступили для поля в equals, например просто вызовите hash();
- Arrays.hashCode();
- null - 0;
- result = 31 * result + fieldHashValue.
- Низко скоросной, но полезный метод: Objects.hash(value1, object2, value3);
- Рекомендации по hash функции:
- Используйте все значимые поля, иначе функция может вызвать неэффективность hashMap;
- Избегаем деталей реализации в описании, чтобы можно было поменять реализацию в будущем.
- clone & Clonable (see example on pages: 93-94):
- Переопределить интерфейс Clonable достаточно, если нет сложных переменных, иначе нужно реализовать метод clone;
- Если сложные переменные final, то их не получится склонировать, но можно рекурсивно вызывать clone (это доделывается вручную в clone);
- В случае если сложные элементы содержат ссылки друг на друга, то их тоже прийдется обновить или скопировать "по умному"(see example on pages: 95-97);
- Можно просто не поддерживать клонирование: throw new cloneNotSupportedException() (see page: 98);
- Возможно, требуется синхронизация;
- Лучший вариант: написать копирующий конструктор - это понятнее и безопаснее.;
- Массивы лучше копировать все же через clone.
- Comparable.
- Метод выполняет сравнение бъекта с указанным:
- Взвращает целое отрицательное, 0, целое положительное, если обект меньше, равен или больше указанного;
- Генерирует ClassCastException, если передан не тот тип обьекта.
- Свойства и советы:
- Позволяет быстро сортировать: Arrays.sort(arr);
- Позволяет взаимодействовать с: TreeSet;
- Используйте стандартные static методы Comparator.comparingInt().thenComparingInt(). Они на 10% медлиннее рукописного компаратора, но очень удобны (See for example page: 105).
4 Классы и интерфейсы
- 1. Минимизируйте доступ к реализации:
- Даем всегда минимальный уровень доступа;
- Исключения - static final поля:
- Примитивные типы - норм;
- Сложные обекты очень окуратно;
- Массивы (see page: 113):
- Преобразуем в неизменяемый список;
- Возвращаем клон массива в паблик методе а сам масив закрытым.
- Есть новых 2 типа доступа на уровне пакета, но они не очень полезные(see pages: 113-114).
- 2. В открытых классах используйте метод доступа а не открытые поля:
- можно открытые, то только с пакетной видемостью;
- даже final не желательно но допустимо.
- 3. Минимизируйте изменяемость классов:
- Как создать:
- Все поля final;
- Все закрытое;
- Доступ только по методам;
- ЕСЛИ ЧТО создаем и возвращаем новый экземпляр. Это называется ФУНКЦИОНАЛЬНЫЙ подход(for example see pages: 118-119).
- Приимущетва:
- Потокобезопасность;
- public static final для часто используемых экземпляров;
- Использование фабоик для кеширования часто используемых объектов;
- Не требуется создавать защищенные копии;
- Не нужно предоставить копирующий конструктор или метод clone();
- Удобный строительный блок для других объектов;
- Кеширование долгоиграющих изменений в не final полях для ускорения.
- Поблемы:
- Потеря ПРОИЗВОДИТЕЛЬНСОТИ при создании нового эеземпляра при каждой операции (примеры решения: StringBuilder, StringBuffeer).
- Особенности создания неизменяемых классов:
- Все поля-объекты тоже долдны быть неизменяемые;
- нужно запретить наследование изменяемого класса, чтобы предупредить изменяемость в наследниках. Это можно сделать с помощью фабричных методов и приватных конструкторов;
- нужно делать копию каждого изменяемого объекта, который входит в изменяемый класс.
- ВЫВОДЫ:
- Классы должны быть неизменяемыми, если нет серьезной причины для их изменяемости;
- Изменяемые классы при возможности должны иметь ограниченное число состояний;
- Все параметры по возможности private final;
- Конструкторы должны создавать полностью инициализированные обекты, все инварианты которых должны быть установлены.
- 4. Предпочитайте композицию наследодванию:
- Можно использовать наследование после композиции (for example see pages: 128-129);
- Решает проблемы изменения унаследованого класса;
- Недостатки:
- проблемы с callBack-ами;
- расход памяти и производительнсоть (на практике не оправдываются).
- Нвследование уместно только когда один обект всегда является подклассом другого;
- Наследование раскрывает детали реализации.
- 5. Проектируйте и документируйте наследование либо запрещайте его:
- Класс должен быть спроектирован для наследования:
- Конструкторы не должны вызывать public, protected методы(for example see page: 135);
- Открытые к наследованию методы не должны вызываться в классе напрямую, а через приватные функции-обертки(pages: 137-138).
- Документация содержать информацию об наследовании(for example see pages: 132-133);
- Протестировать наследуемость можно написав несколько наследников, при этом:
- Не используемые методы закрыть;
- Открыть те, которые необходимы.
- 6. Предпочитайте интерфейсы абстрактным классам:
- Позволяют создавать mixin-ы - добавочную функциональность (see page: 139);
- Для множественного наследования;
- Возможно реализовать на интерфейс еще абстрактный класс, который перекроет часть методов и упростит использование интерфейса.
- 7. Внимательно проектируйте интерфейсы:
- Невозможно изменить;
- Методы по умолчанию помогают плохо. (пример с перекрытием removeIf see on pages: 144-146).
- 8. Используйте интерфейсы только для опеределения типов;
- НЕ используем тлько константные классы:
- Необходимость поддерживать их, даже если они уже не нужны;
- Запутывание пользователей.
- 9. Предпочитайте иерархию классов дескрипторам классов (когда один клас выполняет разные функции в зависемости от параметров типа TAG)(pages: 149-152)
- Запутанная реализация и нечитаемость;
- Слишком много кода;
- Много switch;
- Необходимость каждый раз запутывать класс еще больше.
- 10. Предпочитайте статические классы-члены нестатическим
- static inner ckasses:
- Не содержат ссылку на родителя;
- Можно создать без родителя.
- Анонимные классы имеют ряд ограничений:
- Нелья применить instanceof;
- Нельзя операться на имя класса;
- Нельзя неследоваь и имплиментировать интерфейс одновременно.
- 11. Ограничивайтесь одним классом верхнего уровня на исходный файл.
5. Обобщенное программирование
- 1. Не используйте не сформированные типы: List data = new ArrayList<String>; (правильно так: List<String> data = ...):
- Проблема: отсутствие проверки типов при компиляции;
- Используйте List<?> data. В этом случае типы БУДУТ ПРОВЕРЯТЬСЯ, а там где они не важны, они не будут использоваться (for example see page: 163);
- Исключения, где без не сформированных типов не обойтись:
- List.class (List<String>.class и List<?>.class недопустимы) (page: 164);
- instanceof Set (<String>и <?> являются просто шумом и игнорируются) (page: 164);
- Простой пример как приводить типы <?> (page: 164).
- Резюме:
- Set<Object> - может содержать обьекты любого типа;
- Set<?> - может содержать ТОЛЬКО обьекты некого неизвестного типа;
- Set - не бузопасный способ записи, которы не позволяет компилятору проверять приведение типов.
- Термины:
- List<String> - Параметризированный тип с фактическим параметром String;
- List<E> - Обобщенный тип с формальным параметром E;
- List<?> - Неограниченный тип с символом подстановки;
- List - Несформированный тип;
- <E extends Number> - Ограниченный параметр типа;
- <T extends Comparable<T>> - Рекурсивно граниченный тип;
- <? extends Number> - Ограниченный тип с символом подстановки;
- <? super Number> - Ограниченный тип с символом подстаноEeвки;
- <T extends Enum<T> & Operation> (page: 228 или пункт 6.5);
- static <E> List<E> asList(E[] a) - Обобщенный метод;
- String.class - Токен типа.
- 2. Устраняйте предупреждения о непроверяемом коде:
- Анотация @SuppressWarnings("unchecked"):
- Добавляем, только когда нельзя написать по другому и проверили, что точно работает;
- Используем как можно реже и сужаем область видемости насколько можно (for example see pages: 167-168);
- Добавляем комментарий: почему мы добавили анотацию.
- 3. Предпочитайте списки массивам:
- Массивы кованиантны(if Sub instanceof Super => Sub[ ] instanceof Super[ ]), а списки инвариантны (List<Type1> =!= List<type2>) (for example see pages: 169-170);
- Изза этих и друих (page: 169) различий массивы и обопщенные типы нельзя смешивать: List<E>[ ], new List<String>[ ], new E[ ] => ОШИБОЧНЫ;
- При сообщениях о проблемах постарайтесь заменть массивы списками (for example see pages: 171-173).
- 4-5. Предпочитайте обобщенные типы, обобщенные методы:
- Пример с добавлением обобщенных типов в код: (pages: 178-180);
- Иногда, для обобщения возможных операций, нужно создать "обобщенную фабрику синглтонов" (page: 180);
- Рекурсивное ограничение типа применяется для Comparable: <E extends Comparable E> и можно прочитать как: "Любой тип Е, который можно сравнивать с самим собой".
- 6 используйте ограниченные символы подстановки для повышения гибкости АПИ (pages: 183-188):
- Используем как параметры: <? extends Number>, <? super Number>;
- Правило: производитель extends, потребитель super (page: 185);
- Как созвращаемое значение только <E>;
- Символы подстановки должны быть "не видны" пользователям класса. Если пользователь класса должен думать о типах с символьной подстановкой, вероятно что-то не так с АПИ этого клласса.
- 7. Аккуратно сочитайте обопщенные типы и переменное число аргументов:
- Нельзя использовать массив с переменным числом аргументов вне функции, так как это тот самый массив обобщенных типов, тип которого известен лишь при компиляции (page: 192);
- Аннотацию SafeVarargs следует применять с осторожностью, но все же модно в методах с переменным числом аргументов;
- Применение List более предпочтительно, чем переменное число аргументов (for example see page: 195).
- 8. Пименяйте безопасные с точки зрени типов гетерогенные контейнеры (for example see pages: 196-198).
6. Перечисления и анотации
- 1. Используйте перечисления вместо констант int (page: 203):
- Использовани подкласса для каждого элемента enum, объединение стратегии для групп enum значений (for example see page: 214).
- 2. Используйте поля экземпляров вместо порядковых значений;
- 3. Используйте EnumSet вместо битовых полей (for example see page: 217-219);
- 4. Используйте EnumMap вместо индексирования порядковыми номерами (for example see page: 219-225);
- 5. Имитируйте расширяеміе перечисления с помощью интерфейсов (pages: 225-229):
- Достаточно переопределить недастающие значения в другом enum, реализующим тот же интерфейс (pages: 225-227);
- Недостаток тут - возможное копирование похожей реализации. Для исючения копирования нужно выносить этот код в отдельный класс;
- 6. Предпочитайте анотации схемам именования:
- 7. Последовательно используйте анотацию @Override;
- 8. Используйте интерфейсы-маркеры для определения типов (pages: 242-244):
- Стоит использовать интерфейсы-маркеры, если мы хотим определить тип, и анотацию, елси мы не хотим определять тип.
7. Лямбда выражения и потоки
- 1. Предпочитайте лямбда выражения анонимным классам.
- Если лямбда выражение длиннее 3 строк или слоджное для чтения, то либо найтдите способ его упростить, либо выполните рефакторинг для его устранения.
- 2. Предпочитайте ссылки на методы лямбда выражениям.
- Типы ссылок:
- Статическая - Intager::parseInt - str -> Intager.parseInt(str);
- Ограниченная - Instant::now()::isAfter - Instant then = Instant.now(); t -> then.isAfter(t);
- Неограниченная - String::toLowerCase - str -> str.toLowerCase();
- Конструктор класса - TreeMap<K,V>::new - () -> new TreeMap<K,V>;
- Конструктор массива - int[ ]::new - len -> new int[len].
- 3. Предпочитайте использовать стандартные функциональные интерфейсы: