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:
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:
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:
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:
- Constants
- Member variables
- Constructors
- 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:
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:
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.
- However, Boolean is a class and extends
java.lang.Object. Enums implcitly extendjava.lang.Enum.↩