Enums and Annotations

Photo by Susan Q Yin on Unsplash

Enums and Annotations

·

4 min read

Two special-purpose families of reference types: Enums and Annotations.

Item 34: Use enums instead of int constants

int constants

public static final int APPLE_FUJI = 0;

Disadvantages:

  1. No type safety (other values can be used, even if not present in enum)
  2. Expressiveness (nothing descriptive printed in logs, only values)
  3. No namespace (prefix required)
  4. Constant variables -> compiled (high coupling) to client code -> brittle programs
  5. Performance problems (String constants uses string comparison)

Java enum

They are full-fledged classes and much more powerful than the ones provided by C, C++ or C#.

Basic Idea:

  • export each instance via public static final field
  • no accessible constructor => effectively final => no instantiation or extension (they implicitly extend java.lang.Enum thus no more extensions possible)
  • instance controlled
  • generalization of Singletons

Advantages:

  1. compile-time type safety
  2. namespace, same names can co-exist with different namespace
  3. add or reorder constants, without recompiling clients
  4. descriptive printable strings
  5. add associated, arbitrary methods, fields, implement interface
  6. high quality implementation of Object methods, implements Comparable, Serializable
  7. useful static methods like values(), valueOf(), name() etc.

constant-specific method implementations:

  • declare an abstract method in enum and override it in the constant with constant-specific implementation.
  • to allow reuse we can provide a default implementation instead and override only when needed.
  • prefer this instead of switch statements, as they have to changed every time, someone may just forget
  • consider the strategy enum pattern if some, but not all, enum constants share common behaviours (can use private nested enum for this).

switch in enum

  • augmenting enum types with constant-specific behavior, useful if enum not in our control
  • if enum is in control, but method doesn't belong in enum type

enum constructors can't access static fields except constant variables because when constructor is being executed the static fields wouldn't have initialised. Similarly, enum constants can't access other enum constants from their constructor.

Item 35: Use instance fields instead of ordinals

Never derive a value associated with an enum from its ordinal; store it in an instance field instead. The Enum specification has this to say about 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 36: Use EnumSet instead of bit fields

The EnumSet class combines the conciseness and performance of bit fields with all the many advantages of enum types. It extends the Set interface.

Item 37: Use EnumMap instead of ordinal indexing

It is rarely appropriate to use ordinals to index into arrays: use EnumMap instead. If the relationship you are representing is multidimensional, use EnumMap<..., EnumMap<...>>. This is a special case of the general principle that application programmers should rarely, if ever, use Enum.ordinal. EnumMap use arrays internally, so we pay little in space or time cost for the added clarity, safety, and ease of maintenance.

Item 38: Emulate extensible enums with interfaces

While we cannot write an extensible enum type, we can emulate it by writing an interface to accompany a basic enum type that implements the interface. This allows clients to write their own enums (or other types) that implement the interface. Instances of these types can then be used wherever instances of the basic enum type can be used, assuming APIs are written in terms of the interface.

<T extends Enum<T> & Operation> here extends means that T is a subtype of Enum and Operation as its not possible to extend more than one class.

Item 39: Prefer annotations to naming patterns

There is simply no reason to use naming patterns when you can use annotations instead. Naming patterns were used earlier like in testing the method should start with Test which makes the code brittle as someone may misspell and the test would be silently ignored. Annotations provide a much robust method, having features of repeatable inside containers etc.

Item 40: Consistently use the @Override annotation

We should use the @Override annotation on every method declaration that we believe to override a superclass declaration to avoid overloading or overriding a non-existent method (may be in future). It gives helpful compile time error.

Item 41: Use marker interfaces to define types

A marker interface is an interface that contains no method declarations like Serializable.

Marker interfaces are not obsolete after marker annotations because:

  1. they define a type
  2. they can be targeted more precisely

Make a choice between annotation and interface when using Element.TYPE in annotation. If you want marking other than class/interface then use annotation. If you do want to define a type, do use an interface.

This is it!