Java Enums and Annotations Best Practices

In this article, we will discuss what are Java Enums and Annotations Best Practices we should follow. I referred to these best practices from Effective Java book. Thanks to Joshua Bloch (Author of Effective Java) providing such great best practices and tips to enhance our knowledge.

In my previous article, we have discussed Java generics best practices

1. Use enums instead of int constants

An enum type is a special data type that enables for a variable to be a set of predefined constants. The variable must be equal to one of the values that have been predefined for it. Common examples include compass directions (values of NORTH, SOUTH, EAST, and WEST), the days of the week.

What is a problem with using int constants?

Before enum types were added to the language, a common pattern for representing enumerated types was to declare a group of named int constants, one for each member of the type:
    // int enum pattern - deficient!
    public static final int APPLE_FUJI          = 0;
    public static final int APPLE_PIPPIN        = 1;
    public static final int APPLE_GRANNY_SMITH  = 2;
    public static final int ORANGE_NAVEL        = 0;
    public static final int ORANGE_TEMPLE       = 1;
    public static final int ORANGE_BLOOD        = 2;
This technique, known as the int enum pattern, has many shortcomings.
From the above code, we can find int enum pattern is very deficient in following different way:
  • No way of type safety and no convenience.
  • The compiler won't complain if you pass an apple to a method that expects orange
  • int enums are compile-time constants. They are compiled into the clients that use them.
  • If the int associated with an enum constant is changed, its clients must be recompiled.
  • no easy to translate int enum constants into printable strings (all you see is a number)
You may encounter a variant of this pattern in which String constants are used in place of int constants. This variant, known as the String enum pattern, is even less desirable. While it does provide printable strings for its constants, it can lead naive users to hard-code string constants into client code instead of using field names.

What is the solution?

Java provides an alternative that avoids all the shortcomings of the int and string enum patterns and provides many added benefits. It is the enum type. Here’s how it looks in its simplest form:
    public enum Apple {
        FUJI, PIPPIN, GRANNY_SMITH
    }

    public enum Orange {
        NAVEL, TEMPLE, BLOOD
    }
Java's enum types are full-fledged classes, more powerful than in other languages. 
Advantages of using Enum Type:
  • They're classes that export one instance for each enum constant via a public static final field.
  • They are a generalization of singletons
  • Enums provide compile-time type safety
  • You can translate enums into printable strings by calling toString() method
  • Provide implementations of Object methods
  • Implement Comparable, Serializable
Let's look at the rich enum type with Planet as an example. To associate data with enum constants, declare instance fields and write a constructor that takes the data and stores it in the fields. Enums are by their nature immutable, so all fields should be final. Fields can be public, but it is better to make them private and provide public accessors.
This program takes the earth weight of an object (in any unit) and prints a nice table of the object’s weight on all eight planets (in the same unit):
public enum Planet {
    MERCURY(3.302e+23, 2.439e6),
    VENUS(4.869e+24, 6.052e6),
    EARTH(5.975e+24, 6.378e6),
    MARS(6.419e+23, 3.393e6),
    JUPITER(1.899e+27, 7.149e7),
    SATURN(5.685e+26, 6.027e7),
    URANUS(8.683e+25, 2.556e7),
    NEPTUNE(1.024e+26, 2.477e7);

    private final double mass;
    private final double radius;
    private final double surfaceGravity;

    // universal gravitational constant in m^3 / kg s^2
    private static final double G = 6.67300E-11;

    Planet(double mass, double radius) {
       this.mass = mass;
       this.radius = radius;
       surfaceGravity = G * mass / (radius * radius);
    }

    public double getMass() {
        return mass;
    }

    public double getRadius() {
        return radius;
    }

    public double getSurfaceGravity() {
        return surfaceGravity;
    }

    public double surfaceweight(double mass) {
        return mass * surfaceGravity;
    }
 
    private static void testWeight() {
        double earthWeight = 175.0;
        double mass = earthWeight / Planet.EARTH.getSurfaceGravity();

        System.out.println("Your earth weight = " + earthWeight);
        for(Planet p : Planet.values()) {
            System.out.println("Your Weight on " + p.toString() + " is " + p.surfaceweight(mass));
        }
    }

    public static void main(String[] args) {
        testWeight();
    }
}
Output:
Your earth weight = 175.0
Your Weight on MERCURY is 66.13367201195054
Your Weight on VENUS is 158.38392626475218
Your Weight on EARTH is 175.0
Your Weight on MARS is 66.43069946746834
Your Weight on JUPITER is 442.6939017602279
Your Weight on SATURN is 186.46496971259847
Your Weight on URANUS is 158.3497094835828
Your Weight on NEPTUNE is 198.84611594678634
Read more about enums on Java Enums Tutorial

2. Use instance fields instead of ordinals

As we know that Enum type has an ordinal() method which returns the ordinal of this enumeration constant (its position in its enum declaration, where the initial constant is assigned an ordinal of zero). Most programmers will have no use for this method. It is designed for use by sophisticated enum-based data structures, such as java.util.EnumSet and java.util.EnumMap.

What is the problem using ordinals?

Sometime programmers may be tempted to derive an associated int value from the ordinal. For example:
// Abuse of ordinal to derive an associated value - DONT DO THIS
    public enum Ensemble1 {
        SOLO, DUET, TRIO, QUARTET, QUINTET,
        SEXTET, SEPTET, OCTET, NONET, DECTET;

        public int numMusicians() {
            return ordinal() + 1;
        }
    }
While the above code enum works, it is a maintenance nightmare. If the constants are reordered, the numMusicians() method will break. If you want to add a second enum constant associated with an int value that you’ve already used, you’re out of luck.

What is the solution?

We can solve this problem with a simple solution. Never derive a value associated with an enum from its ordinal; store it in an instance field instead. For example:
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 numMusicians;
        Ensemble(int n) {
        this.numMusicians = n;
    }

    public int numMusicians() {
        return numMusicians;
    }
}
From the above explanation, I would like to conclude here as 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.

3. Use EnumSet instead of bit fields

What is the problem using bit fields?

If the elements of an enumerated type are used primarily in sets, it is traditional to use the int enum pattern, assigning a different power of 2 to each constant. For example:
// Bit field enumeration constants - OBSOLETE!
public static 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_STRIKE_THROUGH    = 1 << 3; // 8

    public Text() {

    }

    // parameter is bitwise OR of zero or more STYLE_ constants
    public void applyStyles(int styles) {
     // hard to interpret a bit field
     // no easy way to iterate over all of the elements represented by a bit field
    }
}
This representation lets us use the bitwise OR operation to combine several constants into a set, known as a bit field:
text.applyStyles(STYLE_BOLD | STYLE_ITALIC);

What is the solution?

We can use EnumSet class to solve this problem. The EnumSet class is used to efficiently represent sets of values drawn from a single enum type. This class implements the Set interface. Each EnumSet is represented as a bit vector. Internally, the EnumSet is represented as a bit vector. If the enum type has 64 or fewer elements, the entire EnumSet is represented with a single long, so its performance is comparable to a bit field
import java.util.EnumSet;

import java.util.Set;

public class ExampleEnumSet {
    /** EnumSet - a modern replacement for bit fields. */
     public static class TextEnumSet {
        public enum Style {
            BOLD, ITALIC, UNDERLINE, STRIKETHROUGH;
        }

        /** Any set can be passed but EnumSet is best. */
        public void applyStyles(Set<Style> styles) {
        }
    }

    public static void main(String[] args) {
        TextEnumSet t2 = new TextEnumSet();
        t2.applyStyles(EnumSet.of(TextEnumSet.Style.BOLD, TextEnumSet.Style.ITALIC));
    }
}
Read more about EnumSet Class APIs with examples - EnumSet Class in Java

4. Use EnumMap instead of ordinal indexing

EnumMap is a specialized Map implementation for use with enum type keys. All of the keys in an enum map must come from a single enum type that is specified, explicitly or implicitly, when the map is created. Enum maps are represented internally as arrays. This representation is extremely compact and efficient.
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.
Below program demonstrate the usage of EnumMap:
import java.util.EnumMap;
import java.util.Map.Entry;

/**
 * EnumMap Demonstration Example
 * @author Ramesh Fadatare
 *
 */
public class EnumMapExample {
     enum Days {
         SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY;
     }

     public static void main(final String[] args) {
         final EnumMap<Days, String> enumMap = new EnumMap<>(Days.class);
         enumMap.put(Days.SUNDAY, "Its Sunday!!");
         enumMap.put(Days.MONDAY, "Its Monday!!");
         enumMap.put(Days.TUESDAY, "Its Tuesday!!");
         enumMap.put(Days.WEDNESDAY, "Its Wednesday!!");
         enumMap.put(Days.THURSDAY, "Its Thursday!!");
         enumMap.put(Days.FRIDAY, "Its Friday!!");
         enumMap.put(Days.SATURDAY, "Its Saturday!!");
  
         for(final Entry<Days, String> entry : enumMap.entrySet()){
             System.out.println(" Key -> " + entry.getKey().SUNDAY);
             System.out.println("Value - >" + entry.getValue());
         }
     }
}
Output:
 Key -> SUNDAY
Value - >Its Sunday!!
 Key -> SUNDAY
Value - >Its Monday!!
 Key -> SUNDAY
Value - >Its Tuesday!!
 Key -> SUNDAY
Value - >Its Wednesday!!
 Key -> SUNDAY
Value - >Its Thursday!!
 Key -> SUNDAY
Value - >Its Friday!!
 Key -> SUNDAY
Value - >Its Saturday!!
Read more about EnumMap on EnumMap Class in Java

5. 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.

6. Prefer annotations to name patterns

What is a problem with naming patterns?

As we know that a long time, it was common to use naming patterns to indicate some program demanded special treatment by a tool or framework. For example, JUnit testing framework required users to name test methods beginning their names with characters 'test'. Below are the disadvantage of using naming patterns:
  • Suppose you accidentally have tsetSafetyOverride() method instead of testSafetyOverride() method. JUnit wouldn't complain, but wouldn't execute the test either leading to false security.
  • No way to ensure they are used only in appropriate ways
  • No good way to associate parameter values with program elements

What is the solution?

Annotations can solve these problems. Below is an annotation type designating simple tests that are run automatically.
// Marker annotation type declaration
import java.lang.annotation.*;

/**
 * Indicates that the annotated method is a test method.
 * Use only on parameterless static methods
 *
 * @Retention means Test annotations should be retained at runtime
 * @Target means the Test annotation is legal only on method declarations (not class, field declarations)
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Test {

}
Let's see how to use above @Test annotation with an example:
/**
 * Here's an example of using the Test annotation.
 * It's called MARKER ANNOTATION because it has no parameters
 */
public class TestDemo {
    // Test should pass
    @Test
    public static void m1() {

    }

    public static void m2() {

    }

    // Test should fail
    @Test
    public static void m3() {
        throw new RuntimeException("Boom");
    }
    public static void m4() {

    }

    // Invalid use of non-static method
    @Test
    public void m5() {

    }

    public static void m6() {

    }

    @Test
    public static void m7() {
        throw new RuntimeException("Crash");
    }
    public static void m8() {

    }
}
Let's discuss one more example by creating a @ExceptionTest annotation. @ExceptionTest annotation indicates that the annotated method is a test method that must throw the designated exception to succeed.
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ExceptionTest {
    Class<? extends Exception> value();
}
Let's write a code to use above annotation:
public class ExceptionTestDemo {
    // Test will pass because this throws an ArithmeticException
    @ExceptionTest(ArithmeticException.class)
    public static void m1() {
        int i = 0;
        i = i / i;
    }

    // Test will fail because this throws an ArrayOutOfBounds exception
    @ExceptionTest(ArithmeticException.class)
    public static void m2() {
        int[] a = new int[0];
        int i = a[1];
    }

    // Test will fail because it doesnt throw an exception
    @ExceptionTest(ArithmeticException.class)
    public static void m3() {

    }
}
There is simply no reason to use naming patterns when you can use annotations instead. Most programmers should use the predefined annotation types that Java provides. Also, consider using the annotations provided by your IDE or static analysis tools. Such annotations can improve the quality of the diagnostic information provided by these tools.

7. Consistently use the Override annotation

The most important annotation provided by Java library is a @Override annotation. This annotation can be used only on method declarations, and it indicates that the annotated method declaration overrides a declaration in a supertype. If you consistently use this annotation, it will protect you from a large class of nefarious bugs. Let's first discuss what is a problem without using @Override annotation and what is a solution using @Override annotation.

What is a problem without using @Override annotation?

Consider the below program prints a size of 260 when it should only print 26 because a Set cannot have duplicates inside of it. The class below was intended to override the equals(), however, this was only overloading it. by default, the equals() method tests for object identity like the == operator.
public class Bigram {

    private final char first;
    private final char second;

    public Bigram(char first, char second) {
        this.first = first;
        this.second = second;
    }

    // The bug is here! equals(), if overrided correctly takes an Object as parameter
    public boolean equals(Bigram b) {
        return b.first == first && b.second == second;
    }

    public int hashCode() {
        return 31 * first + second;
    }

    public static void main(String[] args) {
        Set<Bigram> s = new HashSet<Bigram>();
        for(int i = 0; i < 10 ; i++) {
            for(char ch = 'a'; ch <= 'z'; ch++) {
                s.add(new Bigram(ch, ch));
            }
        }

        System.out.println(s.size()); // size is 260
    }
}
Output:
260
As you can see, output to totally wrong.

What is the solution?

The solution is very simple you should use the @Override annotation on every method declaration that you believe to override a superclass declaration. Let's rewrite above program and use @Override annotation wherever required:
public class Bigram {

    private final char first;
    private final char second;

    public Bigram(char first, char second) {
        this.first = first;
        this.second = second;
    }

    // The bug is here! equals(), if overrided correctly takes an Object as parameter
    @Override
    public boolean equals(Object b) {
     Bigram bigram = (Bigram) b;
        return bigram.first == first && bigram.second == second;
    }

    @Override
    public int hashCode() {
        return 31 * first + second;
    }

    
    public static void main(String[] args) {
        Set<Bigram> s = new HashSet<Bigram>();
        for(int i = 0; i < 10 ; i++) {
            for(char ch = 'a'; ch <= 'z'; ch++) {
                s.add(new Bigram(ch, ch));
            }
        }

        System.out.println(s.size()); // size is 260
    }
}
Let's execute the above program will print below output:
26
Note that the output is correct. We can and should also use @Override annotation on declarations from interfaces. Modern IDEs provide another reason to use @Override annotation. Some have CODE INSPECTIONS. If you enable the appropriate code inspection, the IDE will generate a warning if you have a method that doesn't have an @Override annotation but does override a superclass.
I would like to conclude here as the compiler can protect you from a great many errors if you use the Override annotation on every method declaration that you believe to override a supertype declaration, with one exception.

8. 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. By implementing this interface, a class indicates that its instances can be written to an ObjectOutputStream (or “serialized”).
Marker Interfaces have 2 advantages over marker annotations
  • Define a type that is implemented by instances of the marked class, while marker annotations do not
  • Can be targeted more precisely.
Let's see the sample source code of Serializable interface from Java Library:
 *
 * @see java.io.ObjectOutputStream
 * @see java.io.ObjectInputStream
 * @see java.io.ObjectOutput
 * @see java.io.ObjectInput
 * @see java.io.Externalizable
 * @since   JDK1.1
 */
public interface Serializable {
}
I would like to conclude this best practice as marker interfaces and marker annotations both have their uses. If you want to define a type that does not have any new methods associated with it, a marker interface is a 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.

Please comment if you have any suggestions or feedback about this article would be appreciated.

Free Spring Boot Tutorial | Full In-depth Course | Learn Spring Boot in 10 Hours


Watch this course on YouTube at Spring Boot Tutorial | Fee 10 Hours Full Course

Comments