Skip to main content

Enums

A class restricted to a fixed, named set of instances, decided once at compile time. Instead of passing the string "PENDING" around and misspelling it in one call site out of forty, you declare OrderStatus.PENDING and the compiler refuses to let anything else exist under that name.

«enum»OrderStatusPENDINGCONFIRMEDSHIPPEDDELIVEREDCANCELLED
An enum's members are the only instances that will ever exist.

An enum is still a class

Under the hood, an enum is still a class - the compiler just pre-builds exactly one object per named value and seals the constructor. That is why an enum constant can carry its own fields and override its own methods, the same as any other object:

// A raw int, and a comment doing the compiler's job.
class Order is
// status: 0 = pending, 1 = confirmed, 2 = shipped,
// 3 = delivered, 4 = cancelled
field status: int
 
method refundRate() is
if (status == 2 or status == 3)
return 0.5 // restocking fee once it has shipped
else
return 1.0
 
// Nothing stops status = 9, and nothing explains what 2 means
// to the next person who reads this file.
enum OrderStatus is
PENDING, CONFIRMED,
SHIPPED, DELIVERED, CANCELLED
 
method refundRate() is
switch (this) is
case SHIPPED, DELIVERED:
return 0.5 // restocking fee once it has shipped
default:
return 1.0
 
class Order is
field status: OrderStatus
 
// The rule that used to hide in a comment now lives on the
// type. There is no fifth status to misspell.

Why an enum beats a string or an int constant

Two things this buys you that a raw constant cannot:

  • Compile-time safety. The compiler rejects an out-of-range value at the call site instead of at 3 a.m. in production - OrderStatus.SHIPED (typo intact) is a build failure, while "SHIPED" compiles fine and fails a live order somewhere downstream.
  • Exhaustiveness checking. A switch over an enum can be checked for completeness, so adding RETURNED later surfaces every place that forgot about it - the compiler does the "find every usage" search that a code review would otherwise have to do by hand.

The price is symmetrical: adding a value means touching the enum's own declaration, and every exhaustive switch over it now needs a new case, which is exactly the kind of "vary here" instability the State pattern exists to route through polymorphism instead, once the per-constant behavior outgrows a switch.

Enum constants with fields and per-constant behavior

An enum constant is not limited to a bare name. It can carry its own field values, set through the enum's own constructor, so that each constant is a distinct object with its own data rather than an interchangeable label:

enum VehicleSize {
MOTORCYCLE(2), COMPACT(4), LARGE(7);
 
final int footprintInFeet;
 
VehicleSize(int footprintInFeet) {
this.footprintInFeet = footprintInFeet;
}
}

Some languages go further and let each constant override a method body individually, rather than branching inside one shared method:

enum Operation {
PLUS { public int apply(int a, int b) { return a + b; } },
MINUS { public int apply(int a, int b) { return a - b; } };
 
public abstract int apply(int a, int b);
}

Operation.PLUS.apply(2, 3) and Operation.MINUS.apply(2, 3) each run their own method body - no switch at all. This is a small, contained dose of the same idea the State pattern applies at a larger scale: behavior that varies per value lives on the value, not in a branch that inspects the value.

Enum-as-singleton

Because the compiler guarantees exactly one instance per constant, a single-constant enum is a common, safe way to implement the Singleton pattern in languages like Java - it gets serialization safety and protection against reflection-based double-construction for free, which a hand-rolled singleton class has to earn with extra code:

enum ConfigRegistry {
INSTANCE;
 
private final Map<String, String> settings = new HashMap<>();
 
String get(String key) { return settings.get(key); }
}

Iterating and looking up by name

Every enum comes with two capabilities beyond ordinary classes: enumerating every constant that exists, and looking one up by the exact name it was declared with.

for (OrderStatus status : OrderStatus.values()) {
System.out.println(status);
}
 
OrderStatus parsed = OrderStatus.valueOf("SHIPPED"); // throws if the name doesn't match

values() is how an exhaustive UI dropdown or a validation routine can stay in sync with the enum automatically - add RETURNED to the declaration and every loop over values() picks it up without being told. valueOf is the inverse of toString() for the default case, and it is the reason enum names are usually kept as the literal SCREAMING_CASE strings a serialized payload would contain.

Try it yourself: sketch an enum for a traffic light - RED, YELLOW, GREEN. Which method would you want each constant to override so its behavior differs per value, the way refundRate() differs per OrderStatus? Then imagine a sixth status, RETURNED, being added to OrderStatus - which of the checks above would catch every switch that needs updating, and which would stay silent?

Check yourself

Question 1 of 4

Why can an enum constant override a method, the same way a subclass can?