Command Palette

Search for a command to run...

PHASE 4Beginner Java 5+ ~30 min· topic 10 of 12

Topic 4.10

Enums

In one line

An enum is a class with a fixed, named set of instances, such as SMALL, MEDIUM, LARGE. Enums replace error-prone int and String constants with a real type the compiler checks, and they can carry fields, constructors and behaviour.

Think of it like this

The sizes on a coffee-shop menu: Small, Medium, Large. You can't order a "Medium-ish" or a size 7; the menu is a fixed list. Each size also has facts attached (Small is 200 ml and ₹120). An enum is that menu, written as a type: only the listed values exist, and each can carry its own data.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

Enum
Short for enumeration: a type whose values are a fixed list of named constants, like the days of the week.
Enum constant
One of the named values in an enum, such as MONDAY. Each is a single object that exists exactly once.
Ordinal
A constant's position in the declaration, starting at 0. SMALL.ordinal() is 0.
`values()`
A static method every enum has. It returns an array with all the constants, in the order they were declared.
`valueOf(String)`
A static method that returns the constant with exactly that name, or throws IllegalArgumentException if there's none.
Constant-specific body
A { ... } block after a constant that gives that one constant its own version of a method.
`EnumMap` / `EnumSet`
A map and a set specialised for enum keys. They're fast, compact and keep declaration order.
Exhaustive
Covering every possible value. A switch expression over an enum must handle every constant (or have a default).

Step by step

01Declare an enum and use it

List the constants, separated by commas, in UPPER_SNAKE_CASE. Refer to them through the type: Size.MEDIUM.

A variable of type Size can hold only SMALL, MEDIUM, LARGE or null. A method int price(Size s) can't be called with 42 or "medium"; the compiler rejects it.

Main.javawhole filejava
enum Size { SMALL, MEDIUM, LARGE }

Size s = Size.MEDIUM;
System.out.println(s);              // MEDIUM
System.out.println(s.ordinal());    // 1
System.out.println(s == Size.MEDIUM); // true, == is the right way

02What the compiler actually makes

An enum compiles to a final class (unless some constant has a body) extending java.lang.Enum<Size>. Each constant is a public static final Size field. A static initialiser creates the three objects, passing each one's name and ordinal to the Enum constructor, and stores them in a hidden $VALUES array.

values() returns $VALUES.clone(), a fresh copy each call, so callers can't corrupt the real array. In a hot loop, cache it in a static final field.

Size.java (roughly what javac generates)whole filejava
public final class Size extends java.lang.Enum<Size> {
    public static final Size SMALL  = new Size("SMALL", 0);
    public static final Size MEDIUM = new Size("MEDIUM", 1);
    public static final Size LARGE  = new Size("LARGE", 2);
    private static final Size[] $VALUES = { SMALL, MEDIUM, LARGE };

    private Size(String name, int ordinal) { super(name, ordinal); }

    public static Size[] values() { return $VALUES.clone(); }
    public static Size valueOf(String name) { return Enum.valueOf(Size.class, name); }
}

03Give constants data with fields and a constructor

Put arguments after each constant, end the constant list with a semicolon, then declare fields and a constructor. The constructor is implicitly private, and it runs once for each constant during class initialisation.

Now the facts about each size live with the size. Adding EXTRA_LARGE(500, 220) is one line, and every method that uses ml() or price() works without change.

Main.javawhole filejava
enum Size {
    SMALL(200, 120), MEDIUM(300, 150), LARGE(400, 180);   // semicolon needed

    private final int ml;
    private final int price;

    Size(int ml, int price) {     // implicitly private
        this.ml = ml;
        this.price = price;
    }

    int ml() { return ml; }
    int price() { return price; }
}

04Switch over an enum

Inside a switch on an enum, you write the bare constant names (case SMALL), not Size.SMALL (since Java 21 the qualified form is also allowed).

A switch expression must be exhaustive. Cover all constants and you need no default. Leave one out and javac says the switch expression does not cover all possible input values. That's a gift: add a constant later and the compiler lists every switch to update. A default branch would hide that, so avoid it when you can list the cases.

terminal
$ javac Main.java
── expected output ──
Main.java:5: error: the switch expression does not cover all possible input values
int ml = switch (s) {
^
1 error

05Behaviour per constant

Declare an abstract method in the enum and give each constant a body that implements it. Each constant becomes an instance of its own anonymous subclass.

This replaces a switch that you'd otherwise repeat in many places: the behaviour is defined right next to the constant, and the compiler forces every new constant to implement it. It's the Strategy pattern built into the language (see the System Design course's design patterns).

Main.javawhole filejava
enum Op {
    PLUS("+")  { int apply(int a, int b) { return a + b; } },
    MINUS("-") { int apply(int a, int b) { return a - b; } },
    TIMES("*") { int apply(int a, int b) { return a * b; } };

    final String symbol;
    Op(String symbol) { this.symbol = symbol; }

    abstract int apply(int a, int b);
}

06Name, ordinal and what to store

ordinal() is just the position in the source. Insert a constant in the middle and every later ordinal shifts. Never store ordinals in a database or file; store name() (or, better, an explicit code field you control), and read it back with valueOf or a lookup map.

valueOf is case-sensitive and strict: Size.valueOf("small") throws IllegalArgumentException: No enum constant Size.small. When parsing user input, normalise first (trim().toUpperCase()), or build your own lookup that returns a default.

Name, ordinal and what to storediagram
Rendering diagram…

Try it yourself

  1. 1

    Add a constant and let the compiler help

    In "Enum basics", add EXTRA_LARGE to Size. Compile before touching the switch: read the error, then add the missing case. Predict the first lines of output.

  2. 2

    Add an operation

    In the Op example, add DIVIDE("/") with integer division. What does the compiler say if you forget its body? Predict 7 / 3.

  3. 3

    Parse friendly input

    Call Size.fromCode("xl") in the second example inside a try/catch and print the message. Then make fromCode return MEDIUM as a default instead of throwing, and decide which behaviour is safer for an API.

Code & diagrams

Enum basics: values, ordinal, valueOf, switch Java 14+ New tab

The message uses the canonical name, Main.Size, because Size is nested in Main.

Sign in to run this example in your browser.

Expected output

0 SMALL
1 MEDIUM
2 LARGE
picked LARGE, == LARGE? true
SMALL before LARGE? true
LARGE is 400 ml
No enum constant Main.Size.HUGE
Fields, a constructor and a lookup by code New tab

The static map is filled in a static block, which runs after all constants exist.

Sign in to run this example in your browser.

Expected output

SMALL  200 ml  Rs 120  (0.60 per ml)
MEDIUM 300 ml  Rs 150  (0.50 per ml)
LARGE  400 ml  Rs 180  (0.45 per ml)
code ' m ' -> MEDIUM
Constant-specific behaviour and EnumMap New tab
Sign in to run this example in your browser.

Expected output

7 + 3 = 10
7 - 3 = 4
7 * 3 = 21
uses: {PLUS=9, TIMES=4}
weekdays: [MON, TUE, WED, THU, FRI]
range: [TUE, WED, THU]

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Try to create an enum constant with new

Write Size s = new Size();.

terminal
$ javac Main.java
── what you'll see ──
Main.java:2: error: enum classes may not be instantiated
public class Main { public static void main(String[] args) { Size s = new Size(); } }
^
1 error

Break #2

valueOf with the wrong spelling

Call Size.valueOf("HUGE") (or "small" in lower case) without a try/catch.

terminal
$ java Main
── what you'll see ──
Exception in thread "main" java.lang.IllegalArgumentException: No enum constant Main.Size.HUGE
at java.base/java.lang.Enum.valueOf(Enum.java:293)
at Main$Size.valueOf(Main.java:2)
at Main.main(Main.java:4)

Myth vs fact

Myth

Enums are just named integers, like in C.

Fact

Java enum constants are full objects of their own class, with fields, methods and a type the compiler checks. The ordinal is only a position.

Myth

You should compare enums with equals.

Fact

== is correct and preferred: each constant is a single object. It's also null-safe (x == Size.SMALL can't throw), unlike x.equals(...).

Myth

It's fine to save ordinal() in the database.

Fact

Reordering or inserting constants silently changes ordinals and corrupts stored data. Store the name or an explicit code field.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    An enum with a single constant is the simplest thread-safe, serialization-safe singleton (Effective Java Item 3): class initialisation guarantees one instance, and the JVM's enum serialization rules guarantee no copies. Enum also makes clone() throw.

  • ▸

    EnumSet for up to 64 constants is a RegularEnumSet storing one long bit-vector; contains is a bit test and addAll is a bitwise OR. EnumMap is an array indexed by ordinal. Both iterate in declaration order and beat their hash-based equivalents in speed and memory.

  • ▸

    Since Java 5, switch on an enum compiles to a tableswitch over a synthetic $SwitchMap array (mapping ordinals to case numbers) generated in a helper class, so recompiling the enum with reordered constants doesn't break old switches. For switch expressions over enums with no default, javac adds a hidden default that throws MatchException (Java 21+; IncompatibleClassChangeError before) if an unknown constant arrives from a newer enum version.

  • ▸

    Enum constants with bodies are anonymous subclasses, so Op.PLUS.getClass() is Main$Op$1, not Main$Op. Use getDeclaringClass() to get the enum type. Enum hashCode() is identity-based and differs between JVM runs, so HashMap iteration order over enum keys isn't stable; use EnumMap.

Remember this

  1. 1

    enum Size { SMALL, MEDIUM, LARGE } declares a class named Size with exactly three instances. Each name is a public static final field holding the one and only object for that constant, created when the enum class is initialised. You can't create more: new Size() is a compile error.

  2. 2

    Before Java 5, people wrote static final int SMALL = 0, MEDIUM = 1;. Nothing stopped a method that takes an int size from receiving 42, and printing it showed a meaningless number. With an enum, a parameter of type Size can only ever be one of the three constants (or null), and printing it shows its name.

  3. 3

    Every enum gets useful methods for free: name() (the constant's exact name), ordinal() (its position, starting at 0), values() (a new array of all constants, in declaration order), valueOf("SMALL") (look up by name, throws IllegalArgumentException for unknown names), compareTo (by ordinal) and a toString() that returns the name unless you override it.

  4. 4

    Because each constant is a single object, you compare enums with ==. It's safe (there's never a second SMALL), fast, and null-safe on the left. Enums work in switch, and a switch expression over an enum must cover every constant, so adding a new constant makes the compiler point at every switch you forgot to update (Topic 2.4).

  5. 5

    Enums are full classes. They can have fields, a constructor (always private; it runs once per constant), methods, and even constant-specific bodies where each constant overrides an abstract method with its own behaviour. They can implement interfaces. They can't extend another class (they all extend java.lang.Enum).

  6. 6

    The JDK has collections built for enums: EnumMap (a map keyed by an enum, stored as an array indexed by ordinal) and EnumSet (a set stored as bits in a long). Both are much faster and smaller than HashMap/HashSet, and they iterate in declaration order.

Explain it without notes

01

Why are enums better than int or String constants?

02

What does the compiler generate for enum Size { SMALL, MEDIUM, LARGE }?

03

Why is == the right way to compare enum values?

04

What's the danger of ordinal() and how should you persist enums?

05

How do constant-specific method bodies work, and when would you use them?

Practice

01

Write enum Planet with MERCURY, EARTH, JUPITER and a field surfaceGravity (3.7, 9.8, 24.8). Print what a 50 kg mass weighs (mass times gravity, rounded to one decimal) on each planet.

02

Write enum TrafficLight { RED, GREEN, YELLOW } with a method next() implemented with a switch expression (RED -> GREEN -> YELLOW -> RED). Starting from RED, print five transitions.

03

Count the votes {"TEA", "COFFEE", "TEA", "JUICE", "TEA", "COFFEE"} into an EnumMap<Drink, Integer> and print it.

Trade-offs

  • ↔

    Enums are perfect when the set of values is known at compile time. If values come from configuration or a database and change without a release (product categories, country lists), use a class or a lookup table instead; adding an enum constant needs a recompile and redeploy.

  • ↔

    Constant-specific bodies keep behaviour beside the data, but heavy logic in enums makes them hard to test and couples the type to its uses. For big behaviour, have the enum implement an interface or map constants to strategy objects.

  • ↔

    A switch expression without default gives compile-time exhaustiveness checks, but a library enum that gains a constant can still reach old compiled code at run time and throw. Inside one codebase, prefer no default; across library boundaries, handle the unknown case deliberately.

Done when you can

  • Done when you can declare enums with fields, constructors and methods.

  • Done when you can use values(), valueOf, name() and ordinal() correctly, and know not to persist ordinals.

  • Done when you write exhaustive switch expressions over enums without default.

  • Done when you can implement per-constant behaviour with an abstract method.

  • Done when you reach for EnumMap and EnumSet instead of hash-based collections for enum keys.