Enums And Annotations - Sanjeev435/MyJavaPractiseSets GitHub Wiki

1. Use enums instead of int constants :

  • Old way to decalre a static final int constant is known as enum pattern
  • It provides nothing in the way of type safety and little in the way of expressive power. The compiler won’t complain if you pass an apple to a method that expects an orange, compare apples to oranges with the == operator.
  • Programs that use int enums are brittle. Because int enums are constant variables, their int values are compiled into the clients that use them. If the value associated with an int enum is changed, its clients must be recompiled.
  • There is no easy way to translate int enum constants into printable strings.
  • Enum types are effectively final, by virtue of having no accessible constructors.
  • Enums provide compile-time type safety. If you declare a parameter to be of type Apple, you are guaranteed that any non-null object reference passed to the parameter is one of the three valid Apple values.
  • We can add or reorder constants in an enum type without recompiling its clients because the fields that export the constants provide a layer of insulation between an enum type and its clients: constant values are not compiled into the clients as they are in the int enum patterns.
  • Enums provide high-quality implementations of all the Object methods, they implement Comparable and Serializable, and their serialized form is designed to withstand most changes to the enum type.
  • Enums are by their nature immutable, so all fields should be final.
  • If switch statements on enums are not a good choice for implementing constant-specific behavior on enums.
  • Switches on enums are good for augmenting enum types with constant-specific behavior.
  • A minor performance disadvantage of enums is that there is a space and time cost to load and initialize enum types, but it is unlikely to be noticeable in practice.
  • There is a better way to associate a different behavior with each enum constant: declare an abstract apply method in the enum type, and override it with a concrete method for each constant in a constant-specific class body. Such methods are known as constant-specific method implementations
  • The Strategy Enum Pattern
enum PayrollDay {
     MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY,
     SATURDAY(PayType.WEEKEND), SUNDAY(PayType.WEEKEND);

     private final PayType payType;
     PayrollDay(PayType payType) { 
     	this.payType = payType; 
     }

     PayrollDay() { 
        this(PayType.WEEKDAY); // Default
     }

    int pay(int minutesWorked, int payRate) {
        return payType.pay(minutesWorked, payRate);
     }

     // The strategy enum type
     private enum PayType {
        WEEKDAY {
           int overtimePay(int minsWorked, int payRate) {
               return minsWorked <= MINS_PER_SHIFT ? 0 :
                  (minsWorked - MINS_PER_SHIFT) * payRate / 2;
            }
         },
        WEEKEND {
           int overtimePay(int minsWorked, int payRate) {
               return minsWorked * payRate / 2;
            }
         };

        abstract int overtimePay(int mins, int payRate);
        private static final int MINS_PER_SHIFT = 8 * 60;

        int pay(int minsWorked, int payRate) {
           int basePay = minsWorked * payRate;
           return basePay + overtimePay(minsWorked, payRate);
         }
     }
 }
  • Use enums any time you need a set of constants whose members are known at compile time.
  • It is not necessary that the set of constants in an enum type stay fixed for all time.

2. Use instance fields instead of ordinals :

  • All enums have an ordinal method, which returns the numerical position of each enum constant in its type.
  • Abuse of ordinal : If the constants are reordered, the numberOfMusicians method will break.
// Abuse of ordinal to derive an associated value - DON'T DO THIS
  public enum Ensemble {
      SOLO, DUET, TRIO, QUARTET, QUINTET, SEXTET, SEPTET, OCTET, NONET, DECTET;

      public int numberOfMusicians() { 
      	return ordinal() + 1; 
      }
  }
  • 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; 
      }
  }
  • According to the Enum specification, Ordinal is designed for use by general-purpose enumbased data structures such as EnumSet and EnumMap. Unless you are writing code with this character, you are best off avoiding the ordinal method entirely.

3. Use EnumMap instead of ordinal indexing :

  • There is a very fast Map implementation designed for use with enum keys, known as java.util.EnumMap
  • The reason that EnumMap is comparable in speed to an ordinal-indexed array is that 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
  • EnumMap constructor takes the Class object of the key type: this is a bounded type token, which provides runtime generic type information.
   // 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);
  • This above program can be further shortened by using a stream. Also note that by default stream grouping use HashMap, so in order to use EnumMap, we need to use three-parameter form of Collectors.groupingBy, which allow the caller to specify the map implementation using the mapFactory parameter.
// 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())));
  • Because each phase transition is indexed by a pair of phase enums, you are best off representing the relationship as a map from one enum (the "from" phase) to a map from the second enum (the "to" phase) to the result (the phase transition). The two phases associated with a phase transition are best captured by associating them with the phase transition enum, which can then be used to initialize the nested EnumMap:
// Using a nested EnumMap to associate data with enum pairs
   public enum Phase {
      SOLID, LIQUID, GAS;

   public enum Transition {
      MELT(SOLID, LIQUID), FREEZE(LIQUID, SOLID),
      BOIL(LIQUID, GAS), CONDENSE(GAS, LIQUID),
      SUBLIME(SOLID, GAS), DEPOSIT(GAS, SOLID);

      private final Phase from;
      private final Phase to;

      Transition(Phase from, Phase to) {
         this.from = from;
         this.to = to;
      }

      // Initialize the phase transition map
      private static final Map<Phase, Map<Phase, Transition>> m = Stream.of(values())
                           .collect(groupingBy(t -> t.from, () -> new EnumMap<>(Phase.class), 
                            toMap(t -> t.to, t -> t, (x, y) -> y, () -> new EnumMap<>(Phase.class))));

      public static Transition from(Phase from, Phase to) {
        return m.get(from).get(to);
      }
   }
}

The type of the map is Map<Phase, Map<Phase, Transition>>, which means "map from (source) phase to map from (destination) phase to transition." This map-of-maps is initialized using a cascaded sequence of two collectors. The first collector groups the transitions by source phase, and the second creates an EnumMap with mappings from destination phase to transition. The merge function in the second collector ((x, y) -> y)) is unused; it is required only because we need to specify a map factory in order to get an EnumMap, and Collectors provides telescoping factories.

  • It is rarely appropriate to use ordinals to index into arrays: use EnumMap instead.

4. Use EnumSet instead of bit fields :

   // Bit field enumeration constants - OBSOLETE!
   public class Text {
      public static final int STYLE_BOLD = 1 << 0; // 1
      public static final int STYLE_ITALIC = 1 << 1; // 2
      public static final int STYLE_UNDERLINE = 1 << 2; // 4
      public static final int STYLE_STRIKETHROUGH = 1 << 3; // 8

      // Parameter is bitwise OR of zero or more STYLE_ constants
      public void applyStyles(int styles) { ... }
   }
  • This representation lets you use the bitwise OR operation to combine several constants into a set, known as a bit field:

text.applyStyles(STYLE_BOLD | STYLE_ITALIC);

  • Disadvantages of bit fields
    • It is even harder to interpret a bit field than a simple int enum constant when it is printed as a number.
    • There is no easy way to iterate over all of the elements represented by a bit field.
    • Finally, we have to predict the maximum number of bits we’ll ever need at the time we’re writing the API and choose a type for the bit field (typically int or long) accordingly.
    • Once we’ve picked a type, we can’t exceed its width (32 or 64 bits) without changing the API.
  • The java.util package provides the EnumSet class to efficiently represent sets of values drawn from a single enum type. This class implements the Set interface, providing all of the richness, type safety, and interoperability we get with any other Set implementation.
  • Internally, each EnumSet is represented as a bit vector. If the underlying enum type has sixty-four or fewer elements—and most do—the entire EnumSet is represented with a single long, so its performance is comparable to that of a bit field. Bulk operations, such as removeAll and retainAll, are implemented using bit

5. Emulate extensible enums with interfaces

  • 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. Luckily, there is a nice way to achieve this effect using enum types. The basic idea is to take advantage of the fact that enum types can implement arbitrary interfaces by defining an interface for the opcode type and an enum that is the standard implementation of the interface.
// 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.We can define another enum type that implements this interface and use instances of this new type in place of the base type.
  • For example, suppose you want to define an extension to the operation type shown earlier, consisting of the exponentiation and remainder operations. All you have to do is write an enum type that implements the Operation interface.
//Emulated extension enum
public enum ExtendedOperation implements Operation {
    EXP("^") {
        public double apply(double x, double y) {
            return Math.pow(x, y);
        }
    },
    REMAINDER("%") {
        public double apply(double x, double y) {
            return x % y;
        }
    };

    private final String symbol;

    ExtendedOperation(String symbol) {
        this.symbol = symbol;
    }

    @Override
    public String toString() {
        return symbol;
    }
}

We can now use your new operations anywhere you could use the basic operations, provided that APIs are written to take the interface type (Operation), not the implementation (BasicOperation).

  • Not only is it possible to pass a single instance of an "extension enum" anywhere a "base enum" is expected, but it is possible to pass in an entire extension enum type and use its elements in addition to or instead of those of the base type.
public class TestEnumOperation {
    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));
    }
}

Note that the class literal for the extended operation type (ExtendedOperation.class) is passed from main to test to describe the set of extended operations. The class literal serves as a bounded type token (Item 33). The admittedly complex declaration for the opEnumType parameter (<T extends Enum & Operation> Class) ensures that the Class object represents both an enum and a subtype of Operation, which is exactly what is required to iterate over the elements and perform the operation associated with each one.

  • A second alternative is to pass a Collection<? extends Operation>, which is a bounded wildcard type (Item 31), instead of passing a class object:
public class TestEnumOperation {
    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));
    }
}
  • A minor disadvantage of the use of interfaces to emulate extensible enums is that implementations cannot be inherited from one enum type to another. If the implementation code does not rely on any state, it can be placed in the interface, using default implementations
  • In the case of our Operation example, the logic to store and retrieve the symbol associated with an operation must be duplicated in BasicOperation and ExtendedOperation. In this case it doesn’t matter because very little code is duplicated. If there were a larger amount of shared functionality, you could encapsulate it in a helper class or a static helper method to eliminate the code duplication.
  • The pattern described in this item is used in the Java libraries. For example, the java.nio.file.LinkOption enum type implements the CopyOption and OpenOption 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.

6. Consistently use the Override annotation

  • Use the Override annotation on every method declaration that you believe to override a superclass declaration.
  • There is one minor exception to above rule. If we are writing a class that is not labeled abstract and we believe that it overrides an abstract method in its superclass, we needn’t bother putting the Override annotation on that method.
  • The Override annotation may be used on method declarations that override declarations from interfaces as well as classes.
  • In an abstract class or an interface, however, it is worth annotating all methods that you believe to override superclass or superinterface methods, whether concrete or abstract.

7. 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. For example, consider the Serializable interface
  • 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 you to catch errors at compile time that we couldn’t catch until runtime if you used a marker annotation.
  • Another advantage of marker interfaces over marker annotations is that they can be targeted more precisely. If an annotation type is declared with target ElementType.TYPE, it can be applied to any class or interface.
  • 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
  • Point for when to use marker annotation or interface
    • We must use an annotation if the marker applies to any program element other than a class or interface, because only classes and interfaces can be made to implement or extend an interface
    • If the marker applies only to classes and interfaces and we want to write one or more methods that accept only objects that have this marking then we should use a marker interface in preference to an annotation.
    • If we will never going to write a method that accepts only objects with the marking, then we are probably better off using a marker annotation.
    • The marking is part of a framework that makes heavy use of annotations, then a marker annotation is the clear choice.
    • If we want to define a type that does not have any new methods associated with it, a marker interface is the way to go.
  • If we find ourself writing a marker annotation type whose target is ElementType.TYPE, take the time to figure out whether it really should be an annotation type or whether a marker interface would be more appropriate.
  • If you don’t want to define a type, don’t use an interface and if we do want to define a type, do use an interface.
⚠️ **GitHub.com Fallback** ⚠️