84. Enums

Enums are a special type of class that represent an enumerated type - they’re used to define one or more constants. Sometimes people use strings for this but they aren’t always the best option for checking identifiers and enums are a nicer approach to declaring a set of constants than having lots of static final variables in a class.

Booleans represent an enumerated type1 that can be true or false but, whilst these are English-language keywords, the boolean value true isn’t a string representing the text “true”:

String t = 'true'
Boolean b = true

//This will fail
assert t == b

So let’s take a look at a very simple enum:

A basic enum
enum Months {
    JAN,
    FEB,
    MAR,
    APR,
    MAY,
    JUN,
    JUL,
    AUG,
    SEP,
    OCT,
    NOV,
    DEC
}

// enums help comparisons
assert Months.JAN != Months.DEC
assert Months.APR == Months.APR

// enums aren't text strings
assert Months.OCT != 'Oct'

// Assign a variable to be the value of an enum constant
Months myMonth = Months.AUG

// Iterate through the months
for (month in Months) {
    println month
}

//The values method returns a list of the constants in the enum
println Months.values()

First up you’ll see that the enum keyword replaces class. Next, I’ve named the enum Months and provided a set of three-letter constants for each of the months in a year - these are the enum’s constants. The enum’s constants are accessed using the same approach we use for static variables: Months.JAN. Essentially, the enum’s constant is much the same as a class’s static final variables but the ability to loop through the enum with for (month in Months) {} marks enums as managing a set of constants rather than individual variables/constants.

In the line Months myMonth = Months.AUG you’ll see that enums define types but in a manner different to that we saw in classes. The myMonth variable is of the enum type Months but we don’t create the instance by calling Months myMonth = new Months(). Instead, we assign myMonth to the value of one of the constants as AUG is a constant of type Months.

One more point before moving on, the enum constants don’t have to be declared in upper-case, that’s just the usual approach and mirrors how we declared constants using the static final modifiers. It’s a just a standard approach to style rather than required by the language.

Let’s take a look at another example - this time I’ll create a Gender enum and use it in my Person class:

Another basic enum
import groovy.transform.ToString

enum Gender {
    MALE,
    FEMALE
}

@ToString
class Person {
    String name
    String email
    String mobile
    Gender gender
}

Person jane = new Person(name: 'Jane', gender: Gender.FEMALE)

println jane

There’s probably nothing too new in that example but it helps us take a next step - to give the Gender enum constants a value:

Adding to the enum
import groovy.transform.ToString

enum Gender {
    MALE('male'),
    FEMALE('female')

    final String value

    Gender(value) {
        this.value = value
    }

    @Override
    String toString() {
        value
    }
}

@ToString
class Person {
    String name
    String email
    String mobile
    Gender gender
}

Person jane = new Person(name: 'Jane', gender: Gender.FEMALE)
println jane

Describing that last example can get a litle tricky so I’m going to step through it. First of all, I start the enum declaration as you’d expect:

enum Gender {

Then I list the enum’s constants but they look a little odd. In fact they look like constructor calls:

MALE('male'),
FEMALE('female')

Remember how we don’t call new Gender()? That’s because MALE is a analagous to single static instance of the Gender enum and the call MALE('male') is instantiating MALE via the constructor. Importantly, enum constructors are called internally by the enum and not from any external source - they’re private to the enum. The constructor is called once for each constant.

The next part of the Gender enum, as listed below, declares a member variable (value) and the constructor sets the variable based on the incoming parameter:

final String value

Gender(value) {
    this.value = value
}

I have declared value to be final as I don’t want others to change it. Whilst I could drop the final modifier it’s not a great idea as enums are generally seen as a constant construct.

Lastly, I provide a toString method that helps us when displaying an enum constant:

@Override
String toString() {
    value
}

The order within an enum is important and you must put the list of constants before any other items. I generally prefer to lay them out as follows:

  1. Constants
  2. Member variables
  3. Constructors
  4. Methods

Enums describe not just a set of constants but their order. Groovy provides the built-in next and previous methods that help step through the constants in an enum. Let’s look at a school grades enum and the result of calling next on each constant:

Iterate through an enum
enum Grades {
    FAIL,
    PASS,
    CREDIT
}

println Grades.FAIL.next()
println Grades.PASS.next()
println Grades.CREDIT.next()

Running this script will yield:

PASS
CREDIT
FAIL

Unfortunately, according to Groovy, the next highest grade after CREDIT is FAIL - the next function just loops back to the first constant. This next version will fix that by overriding the default behaviours for next and previous:

Enhancing the enum
enum Grades {
    FAIL,
    PASS,
    CREDIT

    def next() {
        switch (this) {
            case FAIL:
                return PASS
            case PASS:
                return CREDIT
            case CREDIT:
                return CREDIT
        }
    }

    def previous() {
        switch (this) {
            case FAIL:
                return FAIL
            case PASS:
                return FAIL
            case CREDIT:
                return PASS
        }
    }
}

println Grades.FAIL.next()
println Grades.PASS.next()
println Grades.CREDIT.next()

println Grades.FAIL.previous()
println Grades.PASS.previous()
println Grades.CREDIT.previous()

This approach can be really useful when dealing with constants that can be escalated. Think about examples such as a Priority enum with constants such as LOW, MEDIUM and HIGH or a DeliveryTime enum with NORMAL, PRIORITY and EXPRESS.

  1. However, Boolean is a class and extends java.lang.Object. Enums implcitly extend java.lang.Enum.