Enums and Annotations

Item34: Use enums instead of int constants

The int enum pattern is not typesafe. The int enums are constant variables, and their values are compiled into the clients that use them, which means once the int enum changes, the client must be recompiled.

The String enum pattern is just another way of hard-code string constants, and it would lead to performance problems because it relies on string comparisons.

So we should use enum types instead. They are classes that export one instance for each enumeration constant via a public static final field. They are a generation of singletons.

Enums provide compile-time type safety. We can also add or reorder constants in an enum type without recompiling its clients. Enum types also let you add arbitrary methods and fields and implement arbitrary interfaces, which covers a simple collection of enum constants and a full-featured abstraction.

How to write a rich enum type: declare instance fields and write a constructor that takes the data and stores it in the fields.

All enums have a static values method to return an array of its values in the order they are declared.

If an enum is generally useful, it should be a top-level class; if its use is tied to a specific top-level class, it should be a member class of that top-level class.

When we need to write some methods associated to certain enum types, never use switch-case method, but use constant-specific method implementations:

public enum Operation {
	PLUS {public double apply(double x, double y){return x + y;}},
	MINUS {public double apply(double x, double y){return x - y;}},
	TIMES {public double apply(double x, double y){return x * y;}},
	DIVIDE{public double apply(double x, double y){return x / y;}};
	public abstract double apply(double x, double y);
}

The abstract method in the Operation class must be implemented and this is just what we did in the certain enum types. In fact, abstract methods in an enum type must be overridden with concrete methods in all of its constants.

This can be combined with constant-specific data:

public enum Operation {
  PLUS("+") {
  	public double apply(double x, double y) { return x + y; }
  },
  MINUS("-") {
  	public double apply(double x, double y) { return x - y; }
  },
  TIMES("*") {
  	public double apply(double x, double y) { return x * y; }
  },
  DIVIDE("/") {
  	public double apply(double x, double y) { return x / y; }
  };
  private final String symbol;
  Operation(String symbol) { this.symbol = symbol; }
  @Override public String toString() { return symbol; }
  public abstract double apply(double x, double y);
}

Enum types have an automatically generated valueOf(String) method that translates a constant's name into the constant itself. If you override the toString method in an enum type, consider writing a fromString method to translate the custom string representation back to the corresponding enum.

private static final Map<String, Operation> stringToEnum = 
  Stream.of(values()).collect(
  	toMap(Object::toString, e -> e));

// Returns Operation for string, if any
public static Optional<Operation> fromString(String symbol) {
	return Optional.ofNullable(stringToEnum.get(symbol));
}

Enum constructors aren't permitted to access the enum's static fields, with the exception of constant variables, because static fields have not yet been initialized when enum constructors run.

Switches on enums are good for augmenting enum types with constant-specific behavior, which means we are using enums written by others and no constant-specific methods are available. So use enums when everything is under control.

In summary, enums are more readable, safer, and more powerful.

Item35: Use instance fields instead of ordinals

All enums have an ordinal method, which returns the numerical position of each enum constant in its type. People use the method like this:

public enum Ensemble {
  SOLO, DUET, TRIO, QUARTET, QUINTET,
  SEXTET, SEPTET, OCTET, NONET, DECTET;
  public int numberOfMusicians() { return ordinal() + 1; }
}

The usage relies on the order of the enums declared. Whenever the order is changed, everything would be ruined. So never derive a value associated with an enum from its ordinal; store it in an instance field instead:

public enum Ensemble {
  SOLO(1), DUET(2), TRIO(3), QUARTET(4), QUINTET(5),
  SEXTET(6), SEPTET(7), OCTET(8), DOUBLE_QUARTET(8),
  NONET(9), DECTET(10), TRIPLE_QUARTET(12);
  private final int numberOfMusicians;
  Ensemble(int size) { this.numberOfMusicians = size; }
  public int numberOfMusicians() { return numberOfMusicians; }
}

Item36: Use EnumSet instead of bit fields

There is a way to use an enumerated type primarily in sets, and people would assign a different power of 2 to each constants. So we can use OR operation to combine several constants into a set. Also it makes union and intersection efficiently. But this method has too many flaws. we should use EnumSet class to replace it.

This class implements the Set interface, and is represented as a bit vector. It is shorter, clearer, and safer:

public class Text {
  public enum Style { BOLD, ITALIC, UNDERLINE, STRIKETHROUGH }
  // Any Set could be passed in, but EnumSet is clearly best
  public void applyStyles(Set<Style> styles) { ... }
}

// use like this
text.applyStyles(EnumSet.of(Style.BOLD, Style.ITALIC));

Item37: Use EnumMap instead of ordinal indexing

Sometimes in some code, programmers rely on the ordinal of enums to locate its index in an array. The disadvantage is clear, including typesafe issues and hard to modify enum classes. We should use a special Map class designed for this kind scenario:

// Using an EnumMap to associate data with an enum
Map<Plant.LifeCycle, Set<Plant>> plantsByLifeCycle = new EnumMap<>(Plant.LifeCycle.class);
for (Plant.LifeCycle lc : Plant.LifeCycle.values())
	plantsByLifeCycle.put(lc, new HashSet<>());
for (Plant p : garden)
	plantsByLifeCycle.get(p.lifeCycle).add(p);
System.out.println(plantsByLifeCycle);

EnumMap uses such an array internally, but it hides this implementation detail from the programmer, combining the richness and type safety of a Map with the speed of an array.

The EnumMap constructor takes the Class object of the key type: this is a bounded type token, which provides runtime generic type information.

Then this code can be shorter with the help of stream:

// Using a stream and an EnumMap to associate data with an enum
System.out.println(Arrays.stream(garden)
                   .collect(groupingBy(p -> p.lifeCycle,
                                       () -> new EnumMap<>(LifeCycle.class), toSet())));

The behavior of the stream-based versions differs slightly from that of the EmumMap version. The EnumMap version always makes a nested map for each plant lifecycle, while the stream-based versions only make a nested map if the garden contains one or more plants with that lifecycle.

In summary, it is rarely appropriate to use ordinals to index into arrays: use EnumMap instead.

Item38: Emulate extensible enums with interfaces

There is no good way to enumerate over all of the elements of a base type and its extensions. But sometimes it is desirable to let the users of an API provide their own operations, effectively extending the set of operations provided by the API.

We can use interfaces to achieve this with enums:

// Emulated extensible enum using an interface
public interface Operation {
	double apply(double x, double y);
}

public enum BasicOperation implements Operation {
  PLUS("+") {
  	public double apply(double x, double y) { return x + y; }
  },
  MINUS("-") {
  	public double apply(double x, double y) { return x - y; }
  },
  TIMES("*") {
  	public double apply(double x, double y) { return x * y; }
  },
  DIVIDE("/") {
  	public double apply(double x, double y) { return x / y; }
  };
  
  private final String symbol;
  
  BasicOperation(String symbol) {
  	this.symbol = symbol;
  }
    @Override public String toString() {
    return symbol;
  }
}

While the enum type (BasicOperation) is not extensible, the interface type (Operation) is, and it is the interface type that is used to represent operations in APIs.

By implementing the Operation interface, we can define an extension to the operation type shown earlier.

Now write a method that could accept both the base enum type and extended enum type:

public static void main(String[] args) {
  double x = Double.parseDouble(args[0]);
  double y = Double.parseDouble(args[1]);
  test(ExtendedOperation.class, x, y);
}
private static <T extends Enum<T> & Operation> void test(Class<T> opEnumType, double x, double y) {
  for (Operation op : opEnumType.getEnumConstants())
    System.out.printf("%f %s %f = %f%n", x, op, y, op.apply(x, y));
}

This is a bounded type token.

Or, we can do this like:

public static void main(String[] args) {
  double x = Double.parseDouble(args[0]);
  double y = Double.parseDouble(args[1]);
  test(Arrays.asList(ExtendedOperation.values()), x, y);
}
private static void test(Collection<? extends Operation> opSet, double x, double y) {
  for (Operation op : opSet)
  	System.out.printf("%f %s %f = %f%n", x, op, y, op.apply(x, y));
}

This is a bounded wildcard type. It allows the caller to combine operations from multiple implementation types.

The flaw: implementations cannot be inherited from one enum type to another. If there is no state, we can use default implementations in the interface. Or we can encapsulate it in a helper class.

Item39: Prefer annotations to naming patterns

The use of naming patterns, which are just special names, to indicate that some program elements demanded special treatment by a tool or framework, are out of time, and should be replaced by annotations.

// Marker annotation type declaration
import java.lang.annotation.*;
/**
 * Indicates that the annotated method is a test method.
 * Use only on parameterless static methods.
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Test {
}

Here the @Retention and @Target annotations are meta-annotations, and when the @Test annotation is used in practice, it is called marker annotation.

// Program containing marker annotations
public class Sample {
  @Test public static void m1() { } // Test should pass
  public static void m2() { }
  @Test public static void m3() { // Test should fail
  	throw new RuntimeException("Boom");
  }
  public static void m4() { }
  @Test public void m5() { } // INVALID USE: nonstatic method
  public static void m6() { }
  @Test public static void m7() { // Test should fail
  	throw new RuntimeException("Crash");
  }
  public static void m8() { }
}

The Test annotations have no direct effect on the semantics of the Sample class. They serve only to provide information for use by interested programs.

in fact, there is simply no reason to use naming patterns when you can use annotations instead, and all programmers should use the predefined annotation types that Java provides.

Item40: Consistently use the Override annotation

The use of @Override annotation will protect you from a large class of nefarious bugs, mainly method signature, like wrong parameter types, wrong method names. In this way, it could be a good way to inform complier and in consequence it can remind you of this kind of error.

Just use the Override annotation on every method declaration that you believe to override a superclass declaration. In fact, modern IDEs contain the ability to check the @Override annotation and keep an eye on all the things described above.

The rule applies to both class methods and interface methods. Or in a word, use @Override as much as possible.

Item41: Use marker interfaces to define types

A marker interface is an interface that contains no method declarations but merely designates (or "marks") a class that implements the interface as having some property.

Marker interfaces define a type that is implemented by instances of the marked class; marker annotations do not. The existence of a marker interface type allows us to catch errors at compile time that we couldn't catch until runtime if we used a marker annotation. For example, Java's serialization facility uses the Serializable marker interface to indicate that a type is serializable.

Another advantage of marker interfaces over marker annotations is that they can be targeted more precisely.

The chief advantage of marker annotations over marker interfaces is that they are part of the larger annotation facility. Therefore, marker annotations allow for consistency in annotation-based frameworks.

We must use an annotation if the marker applies to any program element other than a class or interface, but we should consider using a mark interface if the marker applies only to classes and interfaces.

Summary: If you want to define a type that does not have any new methods associated with it, a marker interface is the way to go. If you want to mark program elements other than classes and interfaces or to fit the marker into a framework that already makes heavy use of annotation types, then a marker annotation is the correct choice.