What is an enum?
An enum is a special class with a fixed list of objects. You write the list once, and no other objects of that type can exist. Use one when a value can only be one of a few known options, such as a direction, a status, or a size.
The enum is the full menu of allowed choices. Instead of passing around strings like "UP" or numbers like 1, you pass Direction.UP. The compiler then makes sure nobody can pass "UPP" or 7.
public enum Direction {
UP, DOWN, IDLE
}Each name in that list (UP, DOWN, IDLE) is a single object created once by the JVM. There will never be a second Direction.UP.
Why use them instead of strings or ints?
Problem with String / int | What an enum gives you |
|---|---|
status = "DELIVRED" compiles fine and fails at runtime | Typo will not compile |
| You have to remember which numbers mean what | The name is the meaning |
| No way to list all valid values | Status.values() gives you all of them |
switch can miss a case silently | IDE warns you when a switch misses an enum value |
| Cannot attach data or behaviour to a value | Enums can have fields and methods |
Enums are also safe to compare with == because there is only ever one object per constant.
if (direction == Direction.UP) { } // fine, and faster than equalsBasic use
Direction direction = Direction.UP;
System.out.println(direction); // UP
System.out.println(direction.name()); // "UP"
System.out.println(direction.ordinal()); // 0, its position in the list
for (Direction d : Direction.values()) { // all constants
System.out.println(d);
}
Direction parsed = Direction.valueOf("DOWN"); // string to enumBe careful with ordinal(). If someone moves a constant in the list, its number changes, and every number you saved earlier now points to the wrong constant. Never save ordinal() in a database, save name().
Enum with fields and a constructor
An enum can also carry data. Each constant calls the constructor once. Here SMALL(30, 30) passes 30 and 30 to it, and it stores them as width and height in cm. The constructor is always private.
public enum LockerSize {
SMALL(30, 30),
MEDIUM(50, 50),
LARGE(100, 100); // semicolon needed before the fields
private final int widthCm;
private final int heightCm;
LockerSize(int widthCm, int heightCm) { // always private, you cannot write "public"
this.widthCm = widthCm;
this.heightCm = heightCm;
}
public int getWidthCm() {
return widthCm;
}
public boolean fits(int w, int h) {
return w <= widthCm && h <= heightCm;
}
}LockerSize size = LockerSize.MEDIUM;
System.out.println(size.getWidthCm()); // 50
System.out.println(size.fits(20, 20)); // trueNow the size and the dimensions live in one place instead of being spread across if blocks.
Enum with behaviour per constant
Each constant can have its own version of a method. The enum then works like a small table of behaviours, with no if chain at all.
public enum Operation {
ADD {
public double apply(double a, double b) { return a + b; }
},
SUBTRACT {
public double apply(double a, double b) { return a - b; }
},
MULTIPLY {
public double apply(double a, double b) { return a * b; }
};
public abstract double apply(double a, double b);
}double result = Operation.MULTIPLY.apply(3, 4); // 12With a switch on a string, a new operation is easy to forget. Here, a new constant must write its own apply, because the method is abstract. The code does not compile without it.
Enums in a switch
In a switch on an enum, write only the constant name in each case. Do not repeat the enum name.
String label = switch (status) {
case PENDING -> "Waiting";
case SENT_TO_KITCHEN -> "Cooking";
case DELIVERED -> "Done";
case CANCELED -> "Cancelled";
};Use a switch expression (it gives back a value) with the arrow form and no default. Then the compiler tells you when you add a new constant and forget to handle it. A plain switch statement does not. That is a big reason to prefer enums for state.
Enum as a state machine
A common pattern in low-level design (LLD) problems: an order moves through steps, and the enum itself decides which step can come next.
public enum OrderStatus {
PENDING {
public boolean canMoveTo(OrderStatus next) {
return next == SENT_TO_KITCHEN || next == CANCELED;
}
},
SENT_TO_KITCHEN {
public boolean canMoveTo(OrderStatus next) {
return next == DELIVERED || next == CANCELED;
}
},
DELIVERED {
public boolean canMoveTo(OrderStatus next) {
return false; // final state
}
},
CANCELED {
public boolean canMoveTo(OrderStatus next) {
return false; // final state
}
};
public abstract boolean canMoveTo(OrderStatus next);
}if (!current.canMoveTo(OrderStatus.DELIVERED)) {
throw new IllegalStateException("Cannot deliver an order that is " + current);
}For anything more complex than this, use the State pattern instead. See ../../DesignPatterns/BehaviouralPatterns/StatePattern/.
EnumMap and EnumSet
When the key is an enum, use EnumMap instead of HashMap. Inside, it is a plain array where each constant's ordinal is its position, so it is faster and uses less memory. EnumSet is the same idea for sets.
Map<LockerSize, Integer> config = new EnumMap<>(LockerSize.class);
config.put(LockerSize.SMALL, 10);
config.put(LockerSize.LARGE, 4);
Set<Direction> moving = EnumSet.of(Direction.UP, Direction.DOWN);
Set<Direction> all = EnumSet.allOf(Direction.class);Bonus: EnumMap iterates in the order the constants are declared, which is usually the order you want.
Enums and equals/hashCode
You never write equals or hashCode for an enum. Java already gives you correct ones, based on identity (is it the same object?), and you cannot override them. So enums make good map keys. See 15-EqualsAndHashCode.md.
Nested enums
If an enum only makes sense inside one class, declare it there. MenuItem.Category in the restaurant system does this.
public class MenuItem {
private final Category category;
public enum Category {
MAIN, APPETIZER, DESSERT
}
}Use it as MenuItem.Category.MAIN.
Singleton with an enum
A singleton is a class with only one object. The shortest thread safe way to make one in Java is an enum with one constant. The JVM guarantees a single instance, even with serialization or reflection.
public enum Analytics {
INSTANCE;
private int runCount = 0;
public void countRun() {
runCount++;
}
}
// use
Analytics.INSTANCE.countRun();The repo uses the static holder version instead, in SingletonDesignPattern/After/JudgeAnalytics.java. Both are correct, the enum one is just shorter.
Common mistakes
- Saving
ordinal()to a database. Reorder the constants and all old rows now mean something else. Savename(). - Forgetting the semicolon after the last constant when the enum has fields or methods.
- Using
equalswhere==is clearer. Both work.==is the normal style, and it does not crash when the variable is null. valueOfwith unknown text throwsIllegalArgumentException. Wrap it if the input comes from a user.- Enums with too much logic. If a constant needs several methods and its own changing data, use real classes with the Strategy or State pattern instead.
Where you see it in this repo
- Elevator:
Direction(UP, DOWN, IDLE) inOOOD/05-elevator-system - Parking lot:
VehicleSize(SMALL, MEDIUM, LARGE), used as a map key inMap<VehicleSize, List<ParkingSpot>> - Restaurant:
Statusfor orders (OrderStatusabove), and the nestedMenuItem.Category - ATM:
AccountType,TransactionType - Shipping locker:
LockerSize,ShippingStatus
Most of these are plain constant lists. The repo's LockerSize already carries fields (a name, a daily charge, its size) and has an XLARGE constant, so the example above is simplified.