Core Java

How and when to use Enums and Annotations

This article is part of our Academy Course titled Advanced Java.

This course is designed to help you make the most effective use of Java. It discusses advanced topics, including object creation, concurrency, serialization, reflection and many more. It will guide you through your journey to Java mastery! Check it out here!

1. Introduction

In this part of the tutorial we are going to cover yet another two great features introduced into the language as part of Java 5 release along with generics: enums (or enumerations) and annotations. Enums could be treated as a special type of classes and annotations as a special type of interfaces.

The idea of enums is simple, but quite handy: it represents a fixed, constant set of values. What it means in practice is that enums are often used to design the concepts which have a constant set of possible states. For example, the days of week are a great example of the enums: they are limited to Monday, Tuesday, Wednesday, Thursday, Friday, Saturday and Sunday.

From the other side, annotations are a special kind of metadata which could be associated with different elements and constructs of the Java language. Interestingly, annotations have contributed a lot into the elimination of boilerplate XML descriptors used in Java ecosystem mostly everywhere. They introduced the new, type-safe and robust way of configuration and customization techniques.

2. Enums as special classes

Before enums had been introduced into the Java language, the regular way to model the set of fixed values in Java was just by declaring a number of constants. For example:

public class DaysOfTheWeekConstants {
    public static final int MONDAY = 0;
    public static final int TUESDAY = 1;
    public static final int WEDNESDAY = 2;
    public static final int THURSDAY = 3;
    public static final int FRIDAY = 4;
    public static final int SATURDAY = 5;
    public static final int SUNDAY = 6;
} 

Although this approach kind of works, it is far from being the ideal solution. Primarily, because the constants themselves are just values of type int and every place in the code where those constants are expected (instead of arbitrary int values) should be explicitly documented and asserted all the time. Semantically, it is not a type-safe representation of the concept as the following method demonstrates.

public boolean isWeekend( int day ) {
    return( day == SATURDAY || day == SUNDAY );
} 

From logical point of view, the day argument should have one of the values declared in the DaysOfTheWeekConstants class. However, it is not possible to guess that without additional documentation being written (and read afterwards by someone). For the Java compiler the call like isWeekend (100) looks absolutely correct and raises no concerns.

Here the enums come to the rescue. Enums allow to replace constants with the typed values and to use those types everywhere. Let us rewrite the solution above using enums.

public enum DaysOfTheWeek {
    MONDAY,
    TUESDAY,
    WEDNESDAY,
    THURSDAY,
    FRIDAY,
    SATURDAY,
    SUNDAY
} 

What changed is that the class becomes enum and the possible values are listed in the enum definition. The distinguishing part however is that every single value is the instance of the enum class it is being declared at (in our example, DaysOfTheWeek). As such, whenever enum are being used, the Java compiler is able to do type checking. For example:

public boolean isWeekend( DaysOfTheWeek day ) {
    return( day == SATURDAY || day == SUNDAY );
} 

Please notice that the usage of the uppercase naming scheme in enums is just a convention, nothing really prevents you from not doing that.

3. Enums and instance fields

Enums are specialized classes and as such are extensible. It means they can have instance fields, constructors and methods (although the only limitations are that the default no-args constructor cannot be declared and all constructors must be private). Let us add the property isWeekend to every day of the week using the instance field and constructor.

public enum DaysOfTheWeekFields {
    MONDAY( false ),
    TUESDAY( false ),
    WEDNESDAY( false ),
    THURSDAY( false ),
    FRIDAY( false ),
    SATURDAY( true ),
    SUNDAY( true );

    private final boolean isWeekend;

    private DaysOfTheWeekFields( final boolean isWeekend ) {
        this.isWeekend = isWeekend;
    }

    public boolean isWeekend() {
        return isWeekend;
    }
} 

As we can see, the values of the enums are just constructor calls with the simplification that the new keyword is not required. The isWeekend() property could be used to detect if the value represents the week day or week-end. For example:

public boolean isWeekend( DaysOfTheWeek day ) {
    return day.isWeekend();
} 

Instance fields are an extremely useful capability of the enums in Java. They are used very often to associate some additional details with each value, using regular class declaration rules.

4. Enums and interfaces

Another interesting feature, which yet one more time confirms that enums are just specialized classes, is that they can implement interfaces (however enums cannot extend any other classes for the reasons explained later in the Enums and generics section). For example, let us introduce the interface DayOfWeek.

interface DayOfWeek {
    boolean isWeekend();
} 

And rewrite the example from the previous section using interface implementation instead of regular instance fields.

public enum DaysOfTheWeekInterfaces implements DayOfWeek {
    MONDAY() {
        @Override
        public boolean isWeekend() {
            return false;
        }
    },
    TUESDAY() {
        @Override
        public boolean isWeekend() {
            return false;
        }
    },
    WEDNESDAY() {
        @Override
        public boolean isWeekend() {
            return false;
        }
    },
    THURSDAY() {
        @Override
        public boolean isWeekend() {
            return false;
        }
    },
    FRIDAY() {
        @Override
        public boolean isWeekend() {
            return false;
        }
    },
    SATURDAY() {
        @Override
        public boolean isWeekend() {
            return true;
        }
    },
    SUNDAY() {
        @Override
        public boolean isWeekend() {
            return true;
        }
    };
} 

The way we have implemented the interface is a bit verbose, however it is certainly possible to make it better by combining instance fields and interfaces together. For example:

public enum DaysOfTheWeekFieldsInterfaces implements DayOfWeek {
    MONDAY( false ),
    TUESDAY( false ),
    WEDNESDAY( false ),
    THURSDAY( false ),
    FRIDAY( false ),
    SATURDAY( true ),
    SUNDAY( true );

    private final boolean isWeekend;

    private DaysOfTheWeekFieldsInterfaces( final boolean isWeekend ) {
        this.isWeekend = isWeekend;
    }

    @Override
    public boolean isWeekend() {
        return isWeekend;
    }
} 

By supporting instance fields and interfaces, enums can be used in a more object-oriented way, bringing some level of abstraction to rely upon.

5. Enums and generics

Although it is not visible from a first glance, there is a relation between enums and generics in Java. Every single enum in Java is automatically inherited from the generic Enum< T > class, where T is the enum type itself. The Java compiler does this transformation on behalf of the developer at compile time, expanding enum declaration public enum DaysOfTheWeek to something like this:

public class DaysOfTheWeek extends Enum< DaysOfTheWeek > {
    // Other declarations here
} 

It also explains why enums can implement interfaces but cannot extend other classes: they implicitly extend Enum< T > and as we know from the part 2 of the tutorial, Using methods common to all objects, Java does not support multiple inheritance.

The fact that every enum extends Enum< T > allows to define generic classes, interfaces and methods which expect the instances of enum types as arguments or type parameters. For example:

public< T extends Enum < ? > > void performAction( final T instance ) {
    // Perform some action here
} 

In the method declaration above, the type T is constrained to be the instance of any enum and Java compiler will verify that.

6. Convenient Enums methods

The base Enum< T > class provides a couple of helpful methods which are automatically inherited by every enum instance.

MethodDescription
String name()Returns the name of this enum constant, exactly as declared in its enum declaration.
int ordinal()Returns the ordinal of this enumeration constant (its position in its enum declaration, where the initial constant is assigned an ordinal of zero).

Table 1

Additionally, Java compiler automatically generates two more helpful static methods for every enum type it encounters (let us refer to the particular enum type as T).

MethodDescription
T[] values()Returns the all declared enum constants for the enum T.
T valueOf(String name)Returns the enum constant T with the specified name.

Table 2

Because of the presence of these methods and hard compiler work, there is one more benefit of using enums in your code: they can be used in switch/case statements. For example:

public void performAction( DaysOfTheWeek instance ) {
    switch( instance ) {
        case MONDAY:
            // Do something
            break;

        case TUESDAY:
            // Do something
            break;

        // Other enum constants here
    }
} 

7. Specialized Collections: EnumSet and EnumMap

Instances of enums, as all other classes, could be used with the standard Java collection library. However, certain collection types have been optimized for enums specifically and are recommended in most cases to be used instead of general-purpose counterparts.

We are going to look on two specialized collection types: EnumSet< T > and EnumMap< T, ? >. Both are very easy to use and we are going to start with the EnumSet< T >.

The EnumSet< T > is the regular set optimized to store enums effectively. Interestingly, EnumSet< T > cannot be instantiated using constructors and provides a lot of helpful factory methods instead (we have covered factory pattern in the part 1 of the tutorial, How to create and destroy objects).

For example, the allOf factory method creates the instance of the EnumSet< T > containing all enum constants of the enum type in question:

final Set< DaysOfTheWeek > enumSetAll = EnumSet.allOf( DaysOfTheWeek.class );

Consequently, the noneOf factory method creates the instance of an empty EnumSet< T > for the enum type in question:

final Set< DaysOfTheWeek > enumSetNone = EnumSet.noneOf( DaysOfTheWeek.class );

It is also possible to specify which enum constants of the enum type in question should be included into the EnumSet< T >, using the of factory method:

final Set< DaysOfTheWeek > enumSetSome = EnumSet.of(
    DaysOfTheWeek.SUNDAY,
    DaysOfTheWeek.SATURDAY
); 

The EnumMap< T, ? > is very close to the regular map with the difference that its keys could be the enum constants of the enum type in question. For example:

final Map< DaysOfTheWeek, String > enumMap = new EnumMap<>( DaysOfTheWeek.class );
enumMap.put( DaysOfTheWeek.MONDAY, "Lundi" );
enumMap.put( DaysOfTheWeek.TUESDAY, "Mardi" ); 

Please notice that, as most collection implementations, EnumSet< T > and EnumMap< T, ? > are not thread-safe and cannot be used as-is in multithreaded environment (we are going to discuss thread-safety and synchronization in the part 9 of the tutorial, Concurrency best practices).


 

8. When to use enums

Since Java 5 release enums are the only preferred and recommended way to represent and dial with the fixed set of constants. Not only they are strongly-typed, they are extensible and supported by any modern library or framework.

9. Annotations as special interfaces

As we mentioned before, annotations are the syntactic sugar used to associate the metadata with different elements of Java language.

Annotations by themselves do not have any direct effect on the element they are annotating. However, depending on the annotations and the way they are defined, they may be used by Java compiler (the great example of that is the @Override annotation which we have seen a lot in the part 3 of the tutorial, How to design Classes and Interfaces), by annotation processors (more details to come in the Annotation processors section) and by the code at runtime using reflection and other introspection techniques (more about that in the part 11 of the tutorial, Reflection and dynamic languages support).

Let us take a look at the simplest annotation declaration possible:

public @interface SimpleAnnotation {
}

The @interface keyword introduces new annotation type. That is why annotations could be treated as specialized interfaces. Annotations may declare the attributes with or without default values, for example:
public @interface SimpleAnnotationWithAttributes {
    String name();
    int order() default 0;
} 

If an annotation declares an attribute without a default value, it should be provided in all places the annotation is being applied. For example:

@SimpleAnnotationWithAttributes( name = "new annotation" ) 

By convention, if the annotation has an attribute with the name value and it is the only one which is required to be specified, the name of the attribute could be omitted, for example:

public @interface SimpleAnnotationWithValue {
    String value();
}

It could be used like this:

@SimpleAnnotationWithValue( "new annotation" ) 

There are a couple of limitations which in certain use cases make working with annotations not very convenient. Firstly, annotations do not support any kind of inheritance: one annotation cannot extend another annotation. Secondly, it is not possible to create an instance of annotation programmatically using the new operator (we are going to take a look on some workarounds to that in the part 11 of the tutorial, Reflection and dynamic languages support). And thirdly, annotations can declare only attributes of primitive types, String or Class< ? > types and arrays of those. No methods or constructors are allowed to be declared in the annotations.

10. Annotations and retention policy

Each annotation has the very important characteristic called retention policy which is an enumeration (of type RetentionPolicy) with the set of policies on how to retain annotations. It could be set to one of the following values.

PolicyDescription
CLASSAnnotations are to be recorded in the class file by the compiler but need not be retained by the VM at run time
RUNTIMEAnnotations are to be recorded in the class file by the compiler and retained by the VM at run time, so they may be read reflectively.
SOURCEAnnotations are to be discarded by the compiler.

Table 3

Retention policy has a crucial effect on when the annotation will be available for processing. The retention policy could be set using @Retention annotation. For example:

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention( RetentionPolicy.RUNTIME )
public @interface AnnotationWithRetention {
} 

Setting annotation retention policy to RUNTIME will guarantee its presence in the compilation process and in the running application.

11. Annotations and element types

Another characteristic which each annotation must have is the element types it could be applied to. Similarly to the retention policy, it is defined as enumeration (ElementType) with the set of possible element types.

Element TypeDescription
ANNOTATION_TYPEAnnotation type declaration
CONSTRUCTORConstructor declaration
FIELDField declaration (includes enum constants)
LOCAL_VARIABLELocal variable declaration
METHODMethod declaration
PACKAGEPackage declaration
PARAMETERParameter declaration
TYPEClass, interface (including annotation type), or enum declaration

Table 4

Additionally to the ones described above, Java 8 introduces two new element types the annotations can be applied to.

Element TypeDescription
TYPE_PARAMETERType parameter declaration
TYPE_USEUse of a type

Table 5

In contrast to the retention policy, an annotation may declare multiple element types it can be associated with, using the @Target annotation. For example:

import java.lang.annotation.ElementType;
import java.lang.annotation.Target;

@Target( { ElementType.FIELD, ElementType.METHOD } )
public @interface AnnotationWithTarget {
} 

Mostly all annotations you are going to create should have both retention policy and element types specified in order to be useful.

12. Annotations and inheritance

The important relation exists between declaring annotations and inheritance in Java. By default, the subclasses do not inherit the annotation declared on the parent class. However, there is a way to propagate particular annotations throughout the class hierarchy using the @Inherited annotation. For example:

@Target( { ElementType.TYPE } )
@Retention( RetentionPolicy.RUNTIME )
@Inherited
@interface InheritableAnnotation {
}

@InheritableAnnotation
public class Parent {
}

public class Child extends Parent {
} 

In this example, the @InheritableAnnotation annotation declared on the Parent class will be inherited by the Child class as well.

13. Repeatable annotations

In pre-Java 8 era there was another limitation related to the annotations which was not discussed yet: the same annotation could appear only once at the same place, it cannot be repeated multiple times. Java 8 eased this restriction by providing support for repeatable annotations. For example:

@Target( ElementType.METHOD )
@Retention( RetentionPolicy.RUNTIME )
public @interface RepeatableAnnotations {
    RepeatableAnnotation[] value();
}

@Target( ElementType.METHOD )
@Retention( RetentionPolicy.RUNTIME )
@Repeatable( RepeatableAnnotations.class )
public @interface RepeatableAnnotation {
    String value();
};
@RepeatableAnnotation( "repeatition 1" )
@RepeatableAnnotation( "repeatition 2" )
public void performAction() {
    // Some code here
} 

Although in Java 8 the repeatable annotations feature requires a bit of work to be done in order to allow your annotation to be repeatable (using @Repeatable), the final result is worth it: more clean and compact annotated code.

14. Annotation processors

The Java compiler supports a special kind of plugins called annotation processors (using the –processor command line argument) which could process the annotations during the compilation phase. Annotation processors can analyze the annotations usage (perform static code analysis), create additional Java source files or resources (which in turn could be compiled and processed) or mutate the annotated code.

The retention policy (see please Annotations and retention policy) plays a key role by instructing the compiler which annotations should be available for processing by annotation processors.

Annotation processors are widely used, however to write one it requires some knowledge of how Java compiler works and the compilation process itself.

15. Annotations and configuration over convention

Convention over configuration is a software design paradigm which aims to simplify the development process when a set of simple rules (or conventions) is being followed by the developers. For example, some MVC (model-view-controller) frameworks follow the convention to place controllers in the ‘controller’ folder (or package). Another example is the ORM (object-relational mappers) frameworks which often follow the convention to look up classes in ‘model’ folder (or package) and derive the relation table name from the respective class.

On the other side, annotations open the way for a different design paradigm which is based on explicit configuration. Considering the examples above, the @Controller annotation may explicitly mark any class as controller and @Entity may refer to relational database table. The benefits also come from the facts that annotations are extensible, may have additional attributes and are restricted to particular element types. Improper use of annotations is enforced by the Java compiler and reveals the misconfiguration issues very early (on the compilation phase).

16. When to use annotations

Annotations are literally everywhere: the Java standard library has a lot of them, mostly every Java specification includes the annotations as well. Whenever you need to associate an additional metadata with your code, annotations are straightforward and easy way to do so.

Interestingly, there is an ongoing effort in the Java community to develop common semantic concepts and standardize the annotations across several Java technologies (for more information, please take a look on JSR-250 specification). At the moment, following annotations are included with the standard Java library.

AnnotationDescription
@DeprecatedIndicates that the marked element is deprecated and should no longer be used. The compiler generates a warning whenever a program uses a method, class, or field with this annotation.
@OverrideHints the compiler that the element is meant to override an element declared in a superclass.
@SuppressWarningsInstructs the compiler to suppress specific warnings that it would otherwise generate.
@SafeVarargsWhen applied to a method or constructor, asserts that the code does not perform potentially unsafe operations on its varargs parameter. When this annotation type is used, unchecked warnings relating to varargs usage are supressed (more details about varargs will be covered in the part 6 of the tutorial, How to write methods efficiently).
@RetentionSpecifies how the marked annotation is retained.
@TargetSpecifies what kind of Java elements the marked annotation can be applied to.
@DocumentedIndicates that whenever the specified annotation is used those elements should be documented using the Javadoc tool (by default, annotations are not included in Javadoc).
@InheritedIndicates that the annotation type can be inherited from the super class (for more details please refer to Annotations and inheritance section).

Table 6

And the Java 8 release adds a couple of new annotations as well.

AnnotationDescription
@FunctionalInterfaceIndicates that the type declaration is intended to be a functional interface, as defined by the Java Language Specification (more details about functional interfaces are covered in the part 3 of the tutorial, How to design Classes and Interfaces).
@RepeatableIndicates that the marked annotation can be applied more than once to the same declaration or type use (for more details please refer to Repeatable annotations section).

Table 7

17. What’s next

In this section we have covered enums (or enumerations) which are used to represent the fixed set of constant values, and annotations which decorate elements of Java code with metadata. Although somewhat unrelated, both concepts are very widely used in the Java. Despite the fact that in the next part of the tutorial we are going to look on how to write methods efficiently, annotations will be often the part of the mostly every discussion.

18. Download the Source Code

This was a lesson on How to design Classes and Interfaces. You may download the source code here: advanced-java-part-5

Andrey Redko

Andriy is a well-grounded software developer with more then 12 years of practical experience using Java/EE, C#/.NET, C++, Groovy, Ruby, functional programming (Scala), databases (MySQL, PostgreSQL, Oracle) and NoSQL solutions (MongoDB, Redis).
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

4 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Nouaman
Nouaman
8 years ago

Hello,
You said : “And thirdly, annotations can declare only attributes of primitive types, String or Class types and arrays of those.”
I think u forgot enum type

Nice post btw.

Andriy Redko
8 years ago
Reply to  Nouaman

Hi Nouaman,

That’s correct.
Thank you very much for pointing this out.

Best Regards,
Andriy Redko

Musa
4 years ago

Hey dude what about this? (you mentioned: although the only limitations are that the default no-args constructor cannot be declared)

public class TestingEnum{

public static void main(String []args){
System.out.println("Hello World");

Color color = Color.RED;

color.method();

}
}

enum Color{
RED, BLUE, GREEN;

private Color(){
System.out.println("Constructor initialisation for "+this.toString());
}

public void method(){
System.out.println("method invocation");
}

}

## OUTPUT

Constructor initialisation for RED
Constructor initialisation for BLUE
Constructor initialisation for GREEN
method invocation

Andriy Redko
4 years ago
Reply to  Musa

Hi Musa,

My apologies for not being strict on this statement. The assumption behind “default no-args constructor” was to refer to “public constructor without arguments” (this is the one available by default even if not explicitly declared for public classes). Your example is valid, the private (and package private) constructors are allowed.

Best Regards,
Andriy Redko

Back to top button