Skip to content
Theory · Interfaces, enums and records · lesson 5 of 8

Enums

Picture a small deck of four cards: a jack, a queen, a king and an ace. The deck was printed at a factory, and you cannot print a fifth card at home. Each card has its own name, and in the box they lie in order.

How do you keep such a card in a program? You could use a string: String card = "king";. But then the compiler lets through both the typo "kign" and the card "joker", which the deck does not have. A number such as int card = 2; is no better: the code does not say what 2 means, and card = 9 compiles too. You need a type that has only the listed values:

JAVA
public class Main {
    public static void main(String[] args) {
        Card c = Card.KING;
        System.out.println(c);              // KING
        System.out.println(c.ordinal());    // 2
        System.out.println(c == Card.KING); // true
        for (Card x : Card.values()) {
            System.out.print(x + " "); // JACK QUEEN KING ACE
        }
    }
}

enum Card { JACK, QUEEN, KING, ACE }

Let's go through the program step by step:

  1. enum Card { … } after Main declares the type Card with exactly four values. Their names are written in capital letters, separated by commas, like the names of constants.
  2. Card c = Card.KING; reaches a value through the type name and a dot.
  3. println(c) prints the value's name: KING.
  4. c.ordinal() returns the value's position in the declaration, counting from zero: JACK is 0, QUEEN is 1, KING is 2.
  5. c == Card.KING gives true.
  6. The for-each loop goes over the array Card.values() and prints all the cards in order.

What an enum is

An enum is a type whose values are all listed in its declaration. You declare it with the word enum, and its values are called enum constants. Each constant is an object of type Card, and Java creates it exactly once.

What follows from this:

  • there are no new values: for new Card() the compiler answers enum classes may not be instantiated;
  • a variable of type Card cannot be given a string or a number, only one of the four constants or null;
  • two variables holding Card.KING always refer to the same object.

Interviewers ask whether enums may be compared with ==. They may: each constant exists as a single instance, and == gives the same answer as equals.

Here the deck picture is not exact. You can print as many decks as you like, and each has its own king. The constant Card.KING is one for the whole program: nobody copies it or prints it again.

name, ordinal and values

Every enum has ready-made methods:

  • name() returns the constant's name as written in the code: "KING"; println prints the same name;
  • ordinal() returns the constant's position in the declaration, counting from zero;
  • Card.values() returns an array of all the constants in declaration order.

The position depends only on the order in the declaration: reorder the constants, and the positions change. That is why ordinal() is not used as a permanent code of a value, for example to save it to a file.

enum in a switch

An enum is convenient to handle in a switch. In case labels, the constants are written without the type name. Let's count a card's points:

JAVA
int points = switch (c) {
    case JACK -> 2;
    case QUEEN -> 3;
    case KING -> 4;
    case ACE -> 11;
};
System.out.println(points); // 4

As you already know, a switch expression must give a value for any input. Here all four constants are listed, so no default is needed. If you forget one, the compiler answers the switch expression does not cover all possible input values. It will also tell you this when a new card is added to the deck.

A field and a constructor

An enum is the same kind of class, only with objects known in advance. That is why its constants can have fields, a constructor and methods. The points can live right in each card:

JAVA
public class Main {
    public static void main(String[] args) {
        System.out.println(Card.ACE.points); // 11
    }
}

enum Card {
    JACK(2), QUEEN(3), KING(4), ACE(11);

    final int points;

    Card(int points) {
        this.points = points;
    }
}

Let's go through the declaration:

  1. JACK(2) is a constant with an argument: for it, Java calls the constructor Card(2).
  2. A semicolon follows the last constant, and the field and the constructor come after it.
  3. final int points; gives each card its own number of points, which does not change after the constructor.
  4. Card.ACE.points gives 11.

Only Java itself calls an enum's constructor, once for each constant. Now a new card comes into the deck together with its points, and there is no way to forget them.

Common mistake

A constant was compared with a string:

JAVA
Card c = Card.KING;
if (c.equals("KING")) { // ✗
    System.out.println("king");
}

The program prints nothing and reports no error. c is an object of type Card, "KING" is a string, and equals gives false for objects of different types. The compiler would reject c == "KING" with incomparable types: Card and String, but it lets this one through. Compare with the constant: c == Card.KING. If the input is a string, compare the name with it: c.name().equals("KING").

Key points

  • enum declares a type with values listed in advance; you cannot create new ones with new.
  • Each constant exists as a single instance, so enums are compared with ==.
  • name() and println give the constant's name, ordinal() its position from zero, values() an array of all the constants.
  • A switch expression over an enum without default must list every constant; fields and a constructor give each constant its data.