Эффективное программирование Java

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. Предпочитайте анотации схемам именования:
      • Пример описания анотации (page: 230), применения (page: 231) и использования (page: 232);
      • Пример вызова анотируемых методов по рефлексии (page: 232);
      • Анотация с параметром, пример (pages: 233-234);
      • Анотация с параметром-массивом (pages: 235-236);
      • Повторяющиеся однотипные анотации (pages: 236-238).
  • 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. Предпочитайте использовать стандартные функциональные интерфейсы:
      • Есть 3 варианта каждого из этих интерфейсов для int, long, double (eg: IntPredicate page: 254);
      • Стоит задуматься о написании своего интерфейса даже при существовании функционального аналога в случаях(пример: Comporator):
        • Он будет часто использоваться и его имя даст описание действиям;
        • С ним связан строгий контракт;
        • Он выигрывает от пользовательских методов по умолчанию.
      • Анотируйте свои функцональные интерфейсы @FunctionalInterface, что не даст возможность их использования более широко чем для лямбда выражений.
    • 4. Разумно используйте потоки (good example see on page:264):
      • Имя для метода возвращающего элементы потока рекомендуется называть как множественное число имени существительного (пример: primes, words... page: 263).
    • 5. Предпочитайте в потоках функции без побочных эффектов (more discription about threads development see on pages: 265-271)
      • forEach() должна использоватся только для вывода результатов потоковых вычислений, но не для выполнения вычислений, так как она не может быть разпоралелена;
      • collect из класса Colectors. Результат раоты методов этого класса обычно является собранной коллекцией.
    • 6. Предпочитайте коллекции потокам в качестве возвращаемых типов:
      • Collection или подтип есть наилучшим типом возвращаемого значения, так как он является подтипом Iterable и имеет метод stream
      • Массив можно сделать Arrays.asList или Stream.of, при желании, потому он тоже подходит;
      • Не храните в памяти большую последовательность, только чтобы вернуть ее как колекцию;
      • Подумайте о сокращении, например замены листа подобием адаптера;
      • Есть обходные пути для итерации потоков (see code on pages: 272-273).
    • 7. Будте внимательны при параллелизации потоков:
      • Не рабтает паралелизация с(see code on page: 278):
        • Stream.iterable;
        • limit.
      • Выигрыш паралелизма оказывается наибольшим в случае потоков над экземплярами:
        • ArrayList;
        • HashMap;
        • HashSet;
        • ConcorrentHashmap;
        • Массивами;
        • Диапазонами int и long.
      • Абстаркция используемая библиотекой потоков для паралелизации есть Stream.splititerator и Iterable.splititerator;
      • Пример эффективного параллельного вычисления (see code on page: 281);
      • ДАЖЕ НЕ ПЫТАЙТЕСЬ распаралелить конвейер потока, если не уверены, что что это сохранит правильность вычислений и улучшит их скорость.

8. Методы

    • 1. Проверяйте корректность параметров:
      • Чем раньше мы обнаружим ошибку, тем мение трудоемким будет ее нахождение;
      • Не соблюджение этого может привести к нарушению принципа атомарности сбоев (смотри раздел 10.8);
      • Метод Objects.requireNonNull() добавлен в Java7 и теперь нет никакой пичины для проверки значения null вручную;
      • Также могут быть полезны методы: checkFromIndexSize, checkFromIndex, checkIndex;
      • Также можно использовать assert, предварительно задав в опциях флаг командной строки: java -ea (или -enableassertions)(see code and details on page: 285);
      • Особенно важно проверять параметры, которые прямо сразу не используются.
    • 2. При необходимости создавайте защитные копии:
      • Вы должны писать программу оборонительно, исходя из предположения, что клиенты будут пытаться разрушить его инварианты;
      • Обдумайте необходимость копирования входных параметров классов, чтобы избежать их изменения после передачи;
      • Избегайте возврата из класса ссылок на изменяемые обекты.
    • 3. Тщательно проектируйте сигнатуры методов:
      • Тщательно выберайте имена методов;
      • Не заходите слишком далеко в погоне за удобством методов;
      • Избегайте длинный списков параметров;
      • Предпочитайте в качестве типов параметров интерфейсы, а не классы;
      • Предпочитайте 2-х элементные типы перечислений вместо параметров boolean.
    • 4. Перегружайте методы разумно:
      • Пример с перегрузкой методов, который не работает (see example on pages: 294-296);
      • Не перегружайте и функциональные интерфейсы тоже. Включение проверки этого факта компилятором: -Xlint:overloads (see details on pages: 299-300).
    • 5. Используйте методы с переменным колличесвом аргументов с осторожностью;
    • 6. Возвращайте пустые коллекции и массивы а не null. Аргументацию смотри в книге (see details on page: 305):
      • Можно использовать вместо создания пустых объектов: Colections.emptyList(), Colections.emptySet(), Colections.emptyMap();
      • Выделение массива для возврата зачений заранее вредит производительности(see details on page: 307).
    • 7. Возвращайте Optional с осторожностью (see details on pages: 307-312);
    • 8. Пишите документирующие коментарии для открытых элементов API (see details on pages: 312-320):
      • Например, если метод запускает фоновый поток - это должно быть отражено в документации;
      • Документацию к пакету нужно помещать в файл packege-info.java, к модулю к module-info.java (see details on page: 319);
      • Документируйте вопросы потоковой безопасности и возможность сериализации (see details on page: 319);
      • Документирование можно наследовать используя дескриптор {@inheritDoc} (see details on page: 319);
      • Если существует документ описываюий архитектуру класса/пакета/модуля, то документация должна содержать ссылку на него;
      • Более детальная информация про документирования смотри "Haw to write doc comments[23]".

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

    • 1. Минимизируйте область видемости локальных переменных (see details on pages: 321-324):
      • Обьявляйте переменную там, где ее в первый раз используюте
      • Цикл for предпочтительней чем while, так как:
        • Сужает область видемости;
        • Допускает копипаст с теми же переменными;
        • Допускает ополнительные переменные, в том числе и переменную size;
      • Сохраняйте малый размер и точную направленность методов, это способствует минимизации видемости.
    • 2. Предпочитайте циклы for для колекции традиционным циклам for(see details on pages: 324-328):
      • Подумайте про реализацию интерфейса Iterable(даже не реализовую Colection), чобы ваш класс мог быть подвержен обходом for как для колекции.
    • 3. Изучите и используйте возможности библеотек:
      • Random:
        • Используйте Random.nextInt() (see details on pages: 328-329);
        • Для большенства случаев используем ThresdLocalRandom (он быстрее Random в 3,6 раза);
        • Для пулов и паралельных потоков используйте SplittableRandom.
      • Скорость библиотек увеличивается. Некоторые библиотеки переписываются годами;
      • Библиотеки расширяются;
      • Другие разработчики тоже используют библиотеки. Это делает код более простым;
      • Библиотеки достаточно велики, но стоит просмотреть хотя-бы:
        • java.lang
        • java.util
        • java.io
    • 4. Если вам не нужны точные ответы, избегайте float и double (see details on pages: 331-334):
      • Используйте BigDecimal. Он имеет 8 режиов округления.;
      • Используйте int или long в зависемости от размерности вычислений. Вместо дробных частей можно вести подсчет в центах, например;
      • До 9 цифр годится int, до 18 long, иначе только BigDecimal (see details on pages: 334).
    • 5. Предпочитайте примитивные типы упакованным примитивным:
      • Различия между типами:
        • Два упакованных типа могут иметь одинаковое значение но разную идентичность;
        • Упакованный тип может быть null;
        • Примитивный типы более эффективны с точки зрения быстродействия и потребления памяти.
      • "==" к упакованным типам просто сравнивает значение ссылок;
      • Учитывайте, что упакованные типы нужно инициализировать;
      • Учитывайте, что упакованные типы при сравнении и любых действиях система сначала распаковывает и на это уходит время.
    • 6. Избегайте применения строк там, где уместнее другой тип (see details on pages: 338-341):
      • Строки - плохая замена другим типам значений (особенно, что касается хранения вводимых значений);
    • Строки - плохая замена для перечислений;
    • Строки - плохая замена для агрегатных типов;
    • Строки - плохая замена надежным (устойчивым к подделке) ключам.
    • 7. Помните о проблемах производительности при конкотинации строк (see details on pages: 341-342);
    • 8. Для ссылки на обьекты используйте их интерфейсы (see details on pages: 344-348);
    • 9. Предпочитайте интерфейсы рефлексии (see details on page: 346):
    • Вы теряете преимущества проверки типов времени компиляции;
    • Код необходимый для рейлексивного доступа к класам неуклюжий и многословный;
    • Проблемы с производительностью (в 7 раз медленнее);
    • Даже если рефлексия необходима, то постарайтесь ограничеться созданием экземпляра а потом все-же обращайтесь к нему без рефлексии.
  • 10. Пользуйтесь машинно-зависемыми методами с осторожностью;
  • 11. Оптимизируйте осторожно.
      • Старайтесь писать хорошие а не быстрые программы;
      • Старайтесь избегать проэктных решений, которые ограничевают производительность;
      • Измеряйте производительность до и после каждой попытки оптимизайии;
      • Используйте средства профилирования, например jmh (see details on page: 352);
      • Существуеи много мифов о производительности, которые на поверку оказываються полуправдой а то и просто ложью;
      • Не старайтесь писать быстрые программы - старайтесь писать хорошие программы, - скорость прийдет сама;
      • Не забывайте о производительности, когда проектируете систему, особенно, когда проэетируете АПИ, протоколы физического уровня и форматы данных;
      • Первый шаг оптимизации заключается в выборе алгоритма: никакая низкоуровневая оптимизация не сможет скомпенсировать плохой выбор алгоритма.
    • 12. Придержуйтесь общепринятых соглашений по именованию (see details on pages: 353-357).
      • Кроме всего прочего руководствуйтесь сдравым смыслом.

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

    • 1. Используйте исключения только в исключительных ситуациях (see details on pages: 359-362);
    • 2. Используйте для восстановления проверяемые значения, а для програмных ошибок исключения времени выполнения (see details on pages: 362-364):
      • Если у вас возникает сомнения, возмодно ли восстановить состояние и какое исключение генерировать, то генерируйте непроверяемые исключения;
      • По-возможности используйте стандартные исключения;
      • Добавляйте в объект/класс исключения дополнительную информацию, чтобы не заставлять пользователя АПИ анализировать строковую информацию в коде.
    • 3. Избегайте ненужных проверяемых исключений:
      • Если программист все равно ничего не сможет сделать в обработке исключения, кроме как проигнорировать его, то лучше использовать непроверяемое исключение (see details on page: 365);
      • Помним о проблемах работы в многопоточности. Кто будет ловить исключение? (see details on page: 366)
      • Альтернатива проверяемому исключению:
        • вернуть Optional(см раздел 8.7), но в этом случае есть недостаток: пользователь не получит информацию о ситуации;
        • разбить на 2 метода, один из которых проверяет можно ли выполнить действие, а второй выполняет (с оговоркой о потокобезопасности) (see details on pages: 366-367);
        • только если эти 2 подхода не подходят рассматриваем вариант генерации исключения.
    • 4. Предпочитайте использовать стандартные исключения.
      • Чаще всего используемые исключения: (see details on pages: 368-369)
        • IllegalArgumentException - неверное ненулевое значение;
        • IllegalStateException - неверное состояние объекта для вызова метода;
        • NullPointerException - неразрешеное нулевое значение параметра;
        • IndexOutOfBoundsException - индексний параметр за границей допустимого диапазона;
        • ConcurrentModificationException - обнаружено запрешеное паралельное использование объекта;
        • UnsupportedOperationException - объект не поддерживает метод.
      • Никогда не используйте напрямую приведенные ниже исключения. Рассматривайте их как абстрактные. Это поможет отличить ваше ситуацию от общего случая: (see details on pages: 368-369)
        • Exception
        • RuntimeException
        • Throwable
        • Error
      • При наследовании помним, что исключения сериалезуемы(глава 12).
    • 5. Генерируйте исключения, соответствующие абстракции:
    • 6. Документируйте все исключения, которые может генерировать метод:
      • Индификатор @throws используется для описани только проверяемых исключений;
      • Документирвать все непроверяемые исключения - недостижимый идеал;
      • Документацию общих исключений для разных методов класса можно переместить в описание класса.
  • 7. Включайте в сообщение информацию о сбое:
      • Добавляем все параметры, приведше к сбою;
      • НЕ включаем пароли и прочую приватную информцию.
    • 8. Добивайтесь атомарности сбоев (see details on pages: 376-378):
      • Метод, вызов которого завершился сбоем должен оставить должен оставить проверяемый обект в том же состоянии, в каком тот был перед вызовом. Метод, обладающий таким свойством называют атамарным по отношению к сбою (failure anatomic, see details on page: 376);
      • Способы достижения:
        • Неизменяемость объекта;
        • Проверка критических значений перед изменением объекта;
        • Произведение изменений с копией объекта;
        • Использование специального кода восстановления.
      • Иногда соблюдение этого правила имеет слишком высокую цену, в этом случае желательно описать состояние объекта после исключения в документации.
    • 9. Не игнорируйте исключения
      • Если вы игнорируете блок catch, по крайней мере оставте коментарий.

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

  • 1. Синхронизируйте доступ к совместно используемым изменяемым данным:
    • Рассмотрены примеры ошибок в синхрогизации:
        • Использовать атомарность вместо синхронизации - это опасный совет (see details on page: 382);
          • Спецификация языка java гарантирует, что чтение и запись в переменную атамарная операция, если только это не long или double (see details on page: 382).
        • Не используйте Thread.stop(). Лучше иметь boolean переменную с синхронизированным доступом (see details on pages: 382-384);
        • Используйте volatile с осторожностью (see details on page: 385);
          • ++ не является атомарным.
        • Хроший вариант - использовать java.concurent.anatomic.AnatomicLong, это лучше и быстрее чем volatile или syncronized (see details on pages: 385-386);
      • Наилучший вариант - ограничивать изменяемые данные одним потоком;
        • Будте окуратны: некоторые методы могут использовать потоки без Вашего ведома.
      • Вполне допустимо чобы один поток изменял обьект данных, а затем совместно использовал его с другими потоками. Такие обьекты называютяс фактически неизменяемыми, а передача ссылки называется безопасной публикацией (see details on page: 386).
    • 2. Избегайте излишней синхронизации.(see details on pages: 386-394):
      • Никогда не передавайте управление клиенту из синхронизированного метода или блока;
      • Можно использовать для динамически удаляемых элементов CopyOnWriteArrayList. Для большенства применений производительнсоть будет ужастной, но он будет идеален, если мы часто обходим лист, и редко его меняем (see details on pages: 391-392);
      • Как правило нужно выполнять как можно меньше действий внутри синхронизируемой области;
        • Если требуются какие-либо длительные действия - выносите их за пределы области блокировки.
      • Есть 2 способа синхронизации:
        • Внутренняя синхронизация в обекте (StringBuffer);
        • И отсутствие синхронизации внутри, и оставление этого на совесть клиента (StringBuilder).
      • Доступ к статическим полям должен быть синхронизирован внутренне, так как это поле может меняться даже из разных клиентов.(see details on page: 393).
    • 3. Предпочитайте исполнителей заданиям, потоки данных потокам исполнения:
      • Краткий обзор Executors (see details on pages: 394-396);
      • Воздержитесь от написания собственных рабочих очередейб, и от прямой работы с потоком.(see details on page: 395):
        • Используйте задачу как абстракцию;
        • Полезные классы: задание с ветвлением ForkJoinTask, может быть разделено на подзадачи ForkJoinPool.
    • 4. Предпочитайте утилиты параллельности методам wait & notify (see details on pages: 396-402):
      • Высокоуровневые утилиты паралельности:
      • Блокировка голодания потока (thread starvation deadlock) (see details on page: 400):
        • Для определения продолжительности интервалов всегда используйте System.nanoTime a не System.currentTimeMillis, так как метод System.nanoTime более окуратен и точен, на него не влияюют коректировки системных часов;
        • Точное измерение времени - трудная задача. Для этго рекомендуется использовать один из спец каркасов таких как jmh.
      • Как пользоваться wait (see example on page: 400):
        • используем wait только в цикле while.
      • Случаи ложного пробуждения потока (see details on page: 401):
        • notify или notifyAll? Лучше вызывать notifyAll, так как препядствует залипанию программы (see details on page: 401).
    • 5. Докуменируйте безопасность с точки зрения потоков(see details on pages: 402-406):
      • Уровни безопаснисти с точки зрения потоков: (see details on page: 403):
        • Неизменяемый (immutable);
        • unconditionally thread-safe;
        • conditionally thread-safe;
        • not thread-safe;
      • Не совместимый с многопоточностью: thread-hesitate.
      • Пример документирования (see example on page: 404);
      • denial-of-service DoS атака - последствия неправильного использования блокировки с открытым доступом (see example on page: 404):
        • Для предотвращения такой блокировки нужно использовать закрытый обьект блокировки(private lock object) (see example on pages: 404-405):
          • Поля внутренних блокировок должны быть обявленны как final (see details on page: 405).
    • 6. Аккуратно применяйте отложенную инициализацию (see examples on pages: 406-409):
      • Есть значительные накладные расходы для этого подхода, в особенности при многопоточном использовании;
    • Делайте отложенную инициализацию так:
  • 7. Избегайте зависимости от планировщика потоков:
      • Колличество работающих потоков должжно быть не больше колличества ядер;
      • Потоки не долны запускаться, если они не выполняют полезную работу:
        • Правельный размер пула потоков и небольшие (но и не слишком маленькие) задания (see details on page: 410).
      • Поток не должен быть в активном ожидании, постоянно опрашивая состояние занятости обекта (see examples on pages: 410-411);
      • Столкнувшись с программой, которая едва работает изза недостатка времени для потоков:(see details on pages: 411-412)
        • НЕ поддавайтесь соблазну поставить Thread.yeald() - такие решения могут работать по разному на разных java машинах;
        • НЕ попробуйте настроить приоритеты потоков. Ситуация с ними та же;
        • Ищите фундоментальные проблемы в работе с потока.

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

  • 1. Предпочитайте альтернативы сериализации java:
      1. Никогда не сериализуййте не проверенные данные;
        1. Если это невозможно, применяйте фильтр входящих классов (see details on page: 417).
    • 2. Реализуйте интерфейс Serialazible крайне осторожно(see details on pages: 418-421):
      • Сериализация накладывает ограничения на изменение класса, а иногда даже на переименование методов (see details on pageы: 418-419);
      • Повышает вероятность появления ошибок и брешей в системе безопасности;
      • Выпуск новой версии класса сопряжен с большей работой по тестированию;
      • Реализация интерфейса Serialazible должна быть тщательно продумана;
      • Классы, предназначенные для наследования, редко должны реализовывать Serialazible, а интерфейсы - редко его расширят;
      • Внутренние классы не должны реализовывать Serialazible (из-за ссылки на парент класс).
    • 3. Подумайте о применении пользовательской сериализованной формы:
      • Нельзя принимать сереализизованную форму предлагаемую по умолчанию, не обдумав сперва насколько она вас устоавивает;
      • Даже если вы решите, что сеиализованная форма по умолчанию для вас пригодна, имейте ввиду, что зачастую для обеспечения инвариантов и безопасности требуется предоставление метода readObject;
      • Подводные камни сериализации:(see details and example on pages: 424-425)
        • Связывание внутреннего представления;
        • Потребление черезмерной памяти;
        • Слишком много времени;
        • Возможно переполнение стека.
      • Хороший пример сериализации:
      • Помните про синхронизацию (see example on pages: 427-428);
      • Явным образом обьявляйте индефикатор версии сериализации (see example on page: 428).
    • 4. Создавайте защищенные методы readObject:
    • 5. Для управления экземпляром предпочитайте типы перечислений метду readResolve:
      • Синглтон перестает таковым буть, если он Serialazible;
      • Используйте enum.INSTANCE.
    • 6. Подумайте о применении прокси агента сеиализации вместо сериализованных экземпляров (see example on pages: 440-444):
      • Это лучшее реение для сериализации;
      • Проблемы решения:
        • Циклические ссылки;
        • На 14% медлиннее.