Search This Blog

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:

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:

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):

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:

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:

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!publicstaticclassText {
publicstaticfinalintSTYLE_BOLD=1<<0; // 1publicstaticfinalintSTYLE_ITALIC=1<<1; // 2publicstaticfinalintSTYLE_UNDERLINE=1<<2; // 4publicstaticfinalintSTYLE_STRIKE_THROUGH=1<<3; // 8publicText() {
}
// parameter is bitwise OR of zero or more STYLE_ constantspublicvoidapplyStyles(intstyles) {
// 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

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.

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.

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.

publicclassExceptionTestDemo {
// Test will pass because this throws an ArithmeticException@ExceptionTest(ArithmeticException.class)
publicstaticvoidm1() {
int i =0;
i = i / i;
}
// Test will fail because this throws an ArrayOutOfBounds exception@ExceptionTest(ArithmeticException.class)
publicstaticvoidm2() {
int[] a =newint[0];
int i = a[1];
}
// Test will fail because it doesnt throw an exception@ExceptionTest(ArithmeticException.class)
publicstaticvoidm3() {
}
}

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.

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:

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:

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.