Java Enumerations
What Are Enumerations?
It is common to have constants defined with something like the following:
public interface Metal {
public static final int COPPER = 0;
public static final int SILVER = 1;
public static final int GOLD = 2;
}
This is where enumerations come into play. Enumerations in Java are powerful. They can be as simple as just a name or have methods and/or variables, just like classes. The previous example could be written using an enumeration instead with the following:
public enum Metal {
COPPER,
SILVER,
GOLD;
}
Enumerations are written in all uppercase.
Built-in Methods
Java enumerations are implicitly final classes. They extend the java.lang.Enum class. Enumerations come with multiple methods built into them.
ordinal()
This method returns an int. The ordinal value starts at 0 and is calculated based off of the order they are declared. The above enumeration, COPPER = 0, SILVER = 1, and GOLD = 2. If the order these are defined changes, so will the ordinal value. If you are persisting this value, you need to be careful that it doesn't change. If your enumeration is alphabetical, when a new value is added, it could throw off the ordinal values of the rest of the enumeration.
name()
The name() method returns a string of the enumeration name. COPPER would return "COPPER", SILVER would return "SILVER" and so on.
values()
This returns an array of each of the values in the enumeration, sorted in the order they are defined.
valueOf()
This takes a String as a parameter and returns the enumeration value with the associated name. The String parameter needs to be the name value from the name() method. If the enumeration isn't found, an IllegalArgumentException is thrown.
Adding Methods
Methods can be added to enumerations. When adding methods to an enumeration, it is similar to an abstract method on a class. You can do this a few different ways. The first way is to add an abstract method.
public enum Metal {
COPPER() {
@Override
public int cost() {
return 10;
}
},
SILVER() {
@Override
public int cost() {
return 20;
}
},
GOLD() {
@Override
public int cost() {
return 30;
}
};
public abstract int cost();
}
Now on the enumerations, you can invoke the method just as you'd expected with the following:
final var copperCost = Metal.COPPER.cost();
Another way you can do this would be to have a default implementation and then only implement it on the enumerations that are different.
public enum Metal {
COPPER() {
@Override
public int cost() {
return 10;
}
},
SILVER,
GOLD;
public int cost() {
return 30;
}
}
In this example, cost() for both SILVER and GOLD would return 30 and COPPER would return 10.
Constructors
Enumerations can also have constructors. The above example would be better implemented using a constructor.
public enum Metal {
COPPER(10),
SILVER(20),
GOLD(30);
private final int cost;
Metal(final int cost) {
this.cost = cost;
}
public int cost() {
return cost;
}
}
You'll notice the constructor doesn't have an access modifier. This isn't because it is package-private. Enumeration constructors are always private since you can't create instances of them at runtime. Providing the private keyword is optional.
Interfaces
Although enumerations cannot extend a class since they already implicitly extend java.lang.Enum, they can implement interfaces.
Sealed interfaces can also have enumerations in their permits clause. Since an enumeration is implicitly final, you do not need to add a non-sealed or final keyword to them.
public sealed interface Money permits PreciousMetal, FiatCurrency {
}
public enum PreciousMetal implements Money {
GOLD,
SILVER;
}
public enum FiatCurrency implements Money {
DOLLAR,
EURO;
}
This can be very useful when paired with pattern matching.
final Money money = PreciousMetal.GOLD;
switch (money) {
case PreciousMetal.GOLD -> System.out.println("Gold");
case PreciousMetal.SILVER -> System.out.println("Silver");
case FiatCurrency.DOLLAR -> System.out.println("Dollar");
case FiatCurrency.EURO -> System.out.println("Euro");
}
// OR
switch (money) {
case PreciousMetal p -> System.out.println("Precious Metal");
case FiatCurrency f -> System.out.println("Fiat Currency");
}
Equality
Since Java enumerations are constant classes, you can use == to check for equality.
Conclusion
Enumerations are a great way to define a set of constants. They allow you to introduce a new type, making them type-safe. Enumerations also allow you to add methods to your constants, which can be very useful.