Enums: Fields, Methods and Patterns
Java enums are far more powerful than the simple named constants found in other languages. Each enum constant is a full object: it can have fields, constructors, and methods. You can even give each constant its own method body. This makes enums an elegant solution to patterns that would otherwise require complex class hierarchies.
Basic Enum
The simplest enum is a list of named constants. The compiler generates a class that extends
java.lang.Enum
public enum Direction {
NORTH, SOUTH, EAST, WEST
}
public class Main {
public static void main(String[] args) {
Direction d = Direction.NORTH;
// name() returns the declared identifier as a String
System.out.println(d.name()); // NORTH
// ordinal() returns the zero-based position
System.out.println(d.ordinal()); // 0
// valueOf() looks up a constant by its name
Direction east = Direction.valueOf("EAST");
System.out.println(east); // EAST
// == is safe for enums — constants are singletons
System.out.println(d == Direction.NORTH); // true
// toString() defaults to name()
System.out.println(Direction.WEST.toString()); // WEST
}
}
Every enum implicitly implements java.io.Serializablejava.lang.Comparable
Note:
Enum with Fields and Constructor
Each enum constant can carry data. You add fields and a constructor to the enum body. The constructor is always
implicitly private
public enum Planet {
MERCURY(3.303e+23, 2.4397e6),
VENUS (4.869e+24, 6.0518e6),
EARTH (5.976e+24, 6.37814e6),
MARS (6.421e+23, 3.3972e6);
private final double mass; // kg
private final double radius; // metres
// Constructor — always private
Planet(double mass, double radius) {
this.mass = mass;
this.radius = radius;
}
static final double G = 6.67300E-11;
double surfaceGravity() {
return G * mass / (radius * radius);
}
double surfaceWeight(double otherMass) {
return otherMass * surfaceGravity();
}
}
public class Main {
public static void main(String[] args) {
double earthWeight = 75.0; // kg
double mass = earthWeight / Planet.EARTH.surfaceGravity();
for (Planet p : Planet.values()) {
System.out.printf("Weight on %-10s: %.2f N%n",
p, p.surfaceWeight(mass));
}
// Weight on MERCURY : 28.33 N
// Weight on VENUS : 67.90 N
// Weight on EARTH : 75.00 N
// Weight on MARS : 28.46 N
}
}
// Enum with a display label different from the constant name
public enum HttpStatus {
OK(200, "OK"),
CREATED(201, "Created"),
BAD_REQUEST(400, "Bad Request"),
UNAUTHORIZED(401, "Unauthorized"),
NOT_FOUND(404, "Not Found"),
INTERNAL_SERVER_ERROR(500, "Internal Server Error");
private final int code;
private final String reason;
HttpStatus(int code, String reason) {
this.code = code;
this.reason = reason;
}
public int code() { return code; }
public String reason() { return reason; }
// Reverse lookup — find enum by numeric code
public static HttpStatus fromCode(int code) {
for (HttpStatus s : values()) {
if (s.code == code) return s;
}
throw new IllegalArgumentException("Unknown status code: " + code);
}
@Override
public String toString() {
return code + " " + reason;
}
}
// Usage
System.out.println(HttpStatus.NOT_FOUND); // 404 Not Found
System.out.println(HttpStatus.fromCode(201)); // 201 Created
System.out.println(HttpStatus.OK.code()); // 200
Enum Methods
Enums can define any non-abstract method in the enum body. These methods operate on the constant's fields and
can call other methods. You can also override toString()
public enum Day {
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY;
public boolean isWeekend() {
return this == SATURDAY || this == SUNDAY;
}
public boolean isWeekday() {
return !isWeekend();
}
// The next day, wrapping around
public Day next() {
Day[] values = Day.values();
return values[(this.ordinal() + 1) % values.length];
}
public Day previous() {
Day[] values = Day.values();
int prev = (this.ordinal() - 1 + values.length) % values.length;
return values[prev];
}
}
public class Main {
public static void main(String[] args) {
System.out.println(Day.FRIDAY.isWeekend()); // false
System.out.println(Day.SATURDAY.isWeekend()); // true
System.out.println(Day.SUNDAY.next()); // MONDAY
System.out.println(Day.MONDAY.previous()); // SUNDAY
// Print only weekdays
for (Day d : Day.values()) {
if (d.isWeekday()) System.out.print(d + " ");
}
// MONDAY TUESDAY WEDNESDAY THURSDAY FRIDAY
}
}
Enum with Abstract Methods
Each constant can provide its own implementation of an abstract method declared in the enum body. This is the constant-specific method body pattern: essentially a compile-time-verified strategy pattern without subclasses.
public enum Operation {
PLUS("+") {
@Override
public double apply(double x, double y) { return x + y; }
},
MINUS("-") {
@Override
public double apply(double x, double y) { return x - y; }
},
TIMES("*") {
@Override
public double apply(double x, double y) { return x * y; }
},
DIVIDE("/") {
@Override
public double apply(double x, double y) {
if (y == 0) throw new ArithmeticException("Division by zero");
return x / y;
}
};
private final String symbol;
Operation(String symbol) { this.symbol = symbol; }
// Abstract — every constant must implement this
public abstract double apply(double x, double y);
@Override
public String toString() { return symbol; }
}
public class Main {
public static void main(String[] args) {
double x = 10, y = 3;
for (Operation op : Operation.values()) {
System.out.printf("%.1f %s %.1f = %.2f%n",
x, op, y, op.apply(x, y));
}
// 10.0 + 3.0 = 13.00
// 10.0 - 3.0 = 7.00
// 10.0 * 3.0 = 30.00
// 10.0 / 3.0 = 3.33
}
}
This pattern is more maintainable than a switchthisapply
Switch on Enum
Enums are the ideal switchdefault
public enum Season { SPRING, SUMMER, AUTUMN, WINTER }
// Traditional switch statement
public static String describeOld(Season s) {
switch (s) {
case SPRING: return "Mild and blooming";
case SUMMER: return "Hot and sunny";
case AUTUMN: return "Cool and colourful";
case WINTER: return "Cold and snowy";
default: return "Unknown";
}
}
// Modern switch expression (Java 14+) — exhaustive, no fall-through
public static String describe(Season s) {
return switch (s) {
case SPRING -> "Mild and blooming";
case SUMMER -> "Hot and sunny";
case AUTUMN -> "Cool and colourful";
case WINTER -> "Cold and snowy";
// No default needed — all four constants are handled
};
}
// Switch with yield for multi-line arms
public static int monthsInSeason(Season s) {
return switch (s) {
case SPRING, SUMMER, AUTUMN, WINTER -> {
int months = 3;
System.out.println(s + " has " + months + " months");
yield months;
}
};
}
public static void main(String[] args) {
for (Season s : Season.values()) {
System.out.println(s + ": " + describe(s));
}
// SPRING: Mild and blooming
// SUMMER: Hot and sunny
// ...
}
EnumSet and EnumMap
java.util.EnumSetjava.util.EnumMapSetMap
import java.util.*;
public enum Permission {
READ, WRITE, DELETE, ADMIN
}
public class PermissionSystem {
// EnumSet — fast set operations using bit manipulation
public static void demonstrateEnumSet() {
EnumSet<Permission> userPerms = EnumSet.of(Permission.READ, Permission.WRITE);
EnumSet<Permission> adminPerms = EnumSet.allOf(Permission.class);
EnumSet<Permission> noPerms = EnumSet.noneOf(Permission.class);
EnumSet<Permission> readOnly = EnumSet.of(Permission.READ);
// Complement — everything except READ
EnumSet<Permission> notRead = EnumSet.complementOf(readOnly);
System.out.println("Not read: " + notRead); // [WRITE, DELETE, ADMIN]
// Range — ordinal order (READ=0, WRITE=1, DELETE=2)
EnumSet<Permission> basicRange = EnumSet.range(Permission.READ, Permission.DELETE);
System.out.println("Range: " + basicRange); // [READ, WRITE, DELETE]
// Test membership — O(1)
System.out.println(userPerms.contains(Permission.DELETE)); // false
System.out.println(adminPerms.containsAll(userPerms)); // true
}
// EnumMap — map from enum constants to values
public static void demonstrateEnumMap() {
EnumMap<Permission, String> descriptions = new EnumMap<>(Permission.class);
descriptions.put(Permission.READ, "Can view resources");
descriptions.put(Permission.WRITE, "Can create and update resources");
descriptions.put(Permission.DELETE, "Can remove resources");
descriptions.put(Permission.ADMIN, "Full system access");
// Iteration always follows declaration order
descriptions.forEach((perm, desc) ->
System.out.printf("%-10s : %s%n", perm, desc));
}
public static void main(String[] args) {
demonstrateEnumSet();
System.out.println("---");
demonstrateEnumMap();
}
}
The internal representation of EnumSetlongRegularEnumSetaddAllretainAllcontainsAll
Note:
Iterating Enum Values
The compiler-generated values()valueOf(String)
public enum Month {
JANUARY(31), FEBRUARY(28), MARCH(31), APRIL(30),
MAY(31), JUNE(30), JULY(31), AUGUST(31),
SEPTEMBER(30), OCTOBER(31), NOVEMBER(30), DECEMBER(31);
private final int days;
Month(int days) { this.days = days; }
public int days() { return days; }
public int days(int year) {
// Leap year only affects February
if (this == FEBRUARY) {
return isLeapYear(year) ? 29 : 28;
}
return days;
}
private static boolean isLeapYear(int year) {
return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
}
}
public class Main {
public static void main(String[] args) {
// Iterate all values
int totalDays = 0;
for (Month m : Month.values()) {
totalDays += m.days();
}
System.out.println("Days in a normal year: " + totalDays); // 365
// Use streams on values()
long longMonths = java.util.Arrays.stream(Month.values())
.filter(m -> m.days() == 31)
.count();
System.out.println("Months with 31 days: " + longMonths); // 7
// Lookup by name
Month m = Month.valueOf("MARCH");
System.out.println(m.days(2024)); // 31
// February 2024 (leap year)
System.out.println(Month.FEBRUARY.days(2024)); // 29
System.out.println(Month.FEBRUARY.days(2023)); // 28
// Build a lookup map (more efficient than calling valueOf() in a loop)
java.util.Map<Integer, Month> byOrdinal = new java.util.EnumMap<>(Month.class);
for (Month mo : Month.values()) {
byOrdinal.put(mo.ordinal() + 1, mo); // 1-based month number
}
System.out.println(byOrdinal.get(12)); // DECEMBER
}
}
values()values()