Enums in Java

An enum is a class with a fixed set of instances, which makes an invalid value impossible rather than merely unlikely.

Definition

An enum declares a type whose instances are a fixed, named set created once by the JVM. Each constant is a singleton, and no other instance can ever exist.

public enum Status {
    DRAFT, PUBLISHED, ARCHIVED
}

Why enums exist

// Before enums: nothing prevents an invalid value
public static final int STATUS_DRAFT = 0;
public static final int STATUS_PUBLISHED = 1;

setStatus(47);          // compiles happily, and is meaningless
setStatus(Status.PUBLISHED);    // the only values that exist are the valid ones
  • Invalid values become a compile error.
  • The name appears in logs and debuggers rather than a number.
  • A switch over an enum can be checked for exhaustiveness.
  • Behaviour can live on the constant itself.

Built in members

Status s = Status.PUBLISHED;

System.out.println(s.name());        // PUBLISHED
System.out.println(s.ordinal());     // 1, the declaration position
System.out.println(s);               // PUBLISHED, toString defaults to the name

Status parsed = Status.valueOf("DRAFT");     // throws if the name is unknown

for (Status value : Status.values()) {
    System.out.println(value);
}
Never persist ordinal(). Reordering the constants silently changes every stored value. Persist name(), or an explicit code field you control.

Fields, constructors and methods

public enum Plan {

    FREE("Free", 0),
    STANDARD("Standard", 499),
    PREMIUM("Premium", 999);

    private final String label;
    private final int monthlyPrice;

    Plan(String label, int monthlyPrice) {   // implicitly private
        this.label = label;
        this.monthlyPrice = monthlyPrice;
    }

    public String label() {
        return label;
    }

    public int annualPrice() {
        return monthlyPrice * 12;
    }

    public boolean isPaid() {
        return monthlyPrice > 0;
    }
}
System.out.println(Plan.STANDARD.annualPrice());   // 5988

The constructor runs once per constant, when the enum class is initialised. It cannot be public, because no further instances may be created.

Constant specific behaviour

public enum Operation {

    ADD {
        @Override public double apply(double a, double b) { return a + b; }
    },
    SUBTRACT {
        @Override public double apply(double a, double b) { return a - b; }
    },
    MULTIPLY {
        @Override public double apply(double a, double b) { return a * b; }
    };

    public abstract double apply(double a, double b);
}
System.out.println(Operation.MULTIPLY.apply(6, 7));   // 42.0

Each constant supplies its own implementation. Adding a new operation forces you to write its behaviour, which a switch would not.

Enums in switch

String message = switch (status) {
    case DRAFT     -> "Not visible yet";
    case PUBLISHED -> "Live";
    case ARCHIVED  -> "Hidden";
};

No default is needed, and none should be added. Without it, adding a fourth constant makes this switch fail to compile, which is exactly the reminder you want. Note that the constants are written unqualified inside case.

EnumSet and EnumMap

EnumSet<Status> visible = EnumSet.of(Status.PUBLISHED, Status.ARCHIVED);
EnumSet<Status> hidden = EnumSet.complementOf(visible);

EnumMap<Status, Integer> counts = new EnumMap<>(Status.class);
counts.put(Status.DRAFT, 12);

These specialised implementations are backed by bit vectors and arrays indexed by ordinal. They are considerably faster and smaller than HashSet and HashMap for enum keys, and they iterate in declaration order.

Enums can implement interfaces

public enum Currency implements Comparable<Currency> {
    INR, USD, EUR
}

An enum implicitly extends java.lang.Enum, so it cannot extend another class, but it may implement any number of interfaces.

The singleton idiom

public enum Registry {

    INSTANCE;

    private final Map<String, String> values = new ConcurrentHashMap<>();

    public void put(String key, String value) {
        values.put(key, value);
    }
}

A single constant enum is the most robust singleton in Java: the JVM guarantees one instance, and it is safe against reflection and serialisation attacks that defeat a private constructor.

Common mistakes

  • Storing or transmitting ordinal().
  • Adding a default branch to an enum switch, which silences the compiler check.
  • Calling valueOf with unvalidated input and not handling IllegalArgumentException.
  • Comparing with equals where == is safe, correct and null tolerant.
  • Putting mutable state on an enum constant, which is effectively global mutable state.

Best practices

  • Use an enum wherever a value comes from a small fixed set.
  • Compare with ==.
  • Give constants an explicit code field when the value is persisted.
  • Prefer constant specific methods over a switch on the enum inside the enum itself.
  • Use EnumSet and EnumMap for enum keyed collections.

Practice

  1. Write a Day enum with a method reporting whether it is a working day.
  2. Why is Plan(String, int) not allowed to be public?
  3. Add a constant to an enum used in a switch expression with no default and observe the compile error.
  4. Implement a Direction enum where each constant returns its opposite.
  5. Explain why a single constant enum is a safer singleton than a class with a private constructor.

Conclusion

An enum turns a set of valid values into a type. Give constants fields and behaviour, compare with ==, avoid ordinal in stored data, and let the compiler check your switches.

Useful resources

Hand picked references for this topic
Written by Lorens Mishra

Default administrator account created by the installer.

Continue reading

All Java notes →
Java

Records in Java

A record declares an immutable data carrier, and the compiler generates the constructor, accessors, equals, hashCode and toString.

Read more

Discussion

0 comments
Sign in to join the discussion.

No comments yet. Be the first to say something.