Effective Java: Enums and Annotations


Item 30. Используйте enum-ы вместо целочисленных контант.
В Java 1.5 был введён новый тип - enum (перечисление). Перечисление имеет фиксированный набор допустимых значений: это могут быть времена года, планеты солнечной системы, масти игральных карт и т. п. Основная идея enum-ов: это классы, экпортирующие по одному экземпляру для каждого своего значения. Enum-ы - это обобщение синглотонов.
Enum-ы обеспечивают безопасность программы на этапе компиляции. Если вы объявляете параметр enum-типа, то гарантируется, что значение, которое будет передано методу, будет одним из элементов этого enum-а и не null. Попытки передать значение неправильного типа, ровно как и использование == для сравнения элементов различных enum-ов, приведут к ошибкам на этапе компиляции программы.
В enum-ы можно добавлять свои собственные методы, чтобы, например, ассоциировать некоторые данные с конкретными элементами. Можно имплементировать ими интерфейсы; в них уже имплементированы Comparable и Serializable. Есть готовые методы ordinal(), valueOf(String) и toString(). Допустимо добавлять даже абстрактные методы, чтобы обеспечить каждый элемемент перечисления собственной реализацией.
Для ознакомления с другими, реже применяемыми особенностями enum-ов рекомендуется прочитать параграф из книги целиком.

Item 31. Используйте поля-члены enum-ов вместо их порядковых номеров.
Все enum-ы имеют метод ordinal(), возврающий порядковый номер элемента, но его использование почти всегда затрудняет поддержку и развитие кода. Вместо него лучше вводить собственные поля и работать с их значениями.
В спецификации метода ordinal() написано: "Most programmers will have no use for this method. It is designed for use by general-purpose enum-based data structures such as EnumSet and EnumMap." Если вы не пишите подобную структуру, то лучше всего обходить этот метод стороной.

Item 32. Используйте EnumSet вместо битовых полей.
Если элементы перечисляемого типа используются в множествах (set), одним из способов реализации является набор констант-степеней двойки с последующим применением битовой арифметики к ним. Этот путь опасен и неудобен. Вместо него рекомендуется использовать уже готовую реализацию - EnumSet. К слову, если количество элементов в enum-е не более 64х, то в EnumSet-е используется та же самая битовая арифметика, а данные о наборе лежат в одном long-е, что обеспечивает максимальную производительность структуры.

Item 33. Используйте EnumMap вместо индексации номеров.
Аналогично предыдущему параграфу, только Map вместо Set. Имплементация Map, использующая элементы enum-а в качестве ключей, уже готова - EnumMap, и не нужно придумывать ничего своего. Внутри неё для хранения данных используется обычный массив, что обеспечивает хорошую производительность.

Item 34. Эмулируйте наследование от enum-ов с помощью интерфейсов.
Наследование для enum-ов запрещено не случайно. Но в некоторых случаях подобный механизм просто необходим (например, если речь идёт об опкодах, и нужно в клиентском коде уметь создавать свои). Для этого очень удобно объявить единый интерфейс, отделённый от перечисления, и имплементировать его enum-ом.

Item 35. Предпочитайте аннотации вместо соглашений о наименованиях.
До релиза Java 1.5 в программах применялись соглашения о наименованиях, чтобы указать на особое трактование некоторых элементов кода. Например, JUnit изначально требовал называть тестовые методы, начиная со слова test. Такой подход имеет массу очевидных недостатков. Все эти проблемы были успешно решены в Java 1.5 с помощью аннотаций (annotations).
Параграф приводит несколько примеров создания своих аннотаций. Вывод таков, что аннотации - достаточно мощный и полезный инструмент, и теперь нет причин использовать соглашения о наименованиях.

Item 36. Нерпеменно используйте аннотацию Override.
Аннотацию Override можно применять только к объявлениям методов. Она указывает на то, что объявление метода переопределяет его объявление в суперклассе. Повсеместное использование этой аннотации поможет избавиться от проблем, возникающих, когда допускается ошибка в объявлении метода, и вместо переопределения происходит его перегрузка (overloading, это когда два метода имеют одинаковые названия, но разные параметры). Современные среды разработки производят инспекции кода и предупреждают о возможности таких проблем.
Начиная с релиза 1.6, аннотацию Override можно использовать не только в подклассах, но и в подинтерфейсах.

Item 37. Используйте интерфейсы-маркеры (marker interfaces) для определения типов.
Интерфейс-маркер не содержит методов и полей, а только помечает класс, который его имплементирует, как имеющий определённое свойство. Очевидный пример - интерфейс Serializable.
Интерфейсы-маркеры имеют следующие преимущества перед маркирующими аннотациями (Item 35):
  1. Они определяют тип, что позволяет осуществлять проверку на этапе компиляции. Например, метод ObjectOutputStream.write(Object) должен бы в качестве параметра принимать не Object, а Serializable. Но по необъяснимым причинам авторы класса этого не сделали.
  2. Интерфейсы могут наследоваться, что позволяет более точно осуществлять таргетинг. Примером здесь служит интерфейс Set, наследующийся от Collection и не добавляющий никаких методов. На самом деле Set - не совсем интерфейс-маркер, так как он переопределяет контракты некоторых методов интерфейса Collection.
Преимущества аннотаций-маркеров перед интерфейсами:
  1. В аннотацию можно легко вложить со временем дополнительную информацию, что невозможно для интерфейса. Интерфейс вообще нельзя изменять после того, как его имплементировали.
  2. Аннотации можно применять не только к классам и интерфейсам, но и к другим программным элементам, таким, как члены или любые объявления. Поэтому именно их зачастую используют в различных фреймворках.
В конечном итоге, аннотацию-маркер следует использовать, если её нужно применять не только к типам, но и к другим программным элементам, либо если маркер, возможно, будет эволюционировать. В остальных случаях, особенно если будут методы, принимающие только маркированные объекты, лучше применять интерфейс.

P.S. На андроиде enum-ы не очень хороши и рекомендуется их избегать. Читать.

1 комментарий:

  1. "На андроиде enum-ы не очень хороши и рекомендуется их избегать".

    Все стало гораздо лучше после 2.2 и появления JIT.

    ОтветитьУдалить