Dev Learning Lab

The labs on this site need JavaScript to run. The same content is written out below.

Learn › Java › Generics, enums, and annotations

Enum classes

Why does an enum saved as a number come back as a different constant?

An enum is a type that can have only a fixed set of values, such as the status of an order. Each constant is an object of which there is only one in the program, so constants can be compared with ==, and they can also have fields and methods. But if you save a constant to a database by writing down its position in the declaration (its ordinal), the same number refers to a different constant once the enum declaration is changed later.

Checked against real execution · 45 combinations were actually compiled and run on JDK 21 and checked against the results

Concepts in this lab

Enum class
This is the syntax for creating a type that can have only a few fixed values. When you declare enum Status { ORDERED, PAID }, ORDERED and PAID each become an object of type Status, and these objects are created only once and no more can be created with new. There is only one object for each constant, so constants can be compared with == and can also be used in a switch. An enum is also a class, so it can have fields, constructors, and methods, which lets you attach a display name or a code value to each constant. Unlike representing a state with a string or a number, using a value that is not defined is a compile-time error, so enums are used to represent states and kinds.
ordinal() and name()
These are two methods that every enum constant has. ordinal() returns the position of the constant in the declaration, counted from 0, and name() returns the name of the constant, as written in the declaration, as a string. ordinal() is determined by the declaration order, so its value changes when a constant is inserted in the middle or the order is changed. For this reason, if you save ordinal() values in a database or a file, the same number can later be read as a different constant. When saving, use name() or the value of a field that you define separately in the enum.
valueOf method of an enum class
This is a static method that takes a name string and returns the enum constant with that name, and it is declared automatically in every enum. Status.valueOf("PAID") returns Status.PAID. The name must match exactly, including case, and when there is no such constant, the method does not return null but throws an IllegalArgumentException. So when you pass a value that cannot be trusted, such as a string entered by a user, you must be prepared for the exception. values(), which returns an array containing all the constants in declaration order, is declared together with it.
Identity and equality
Identity means that two references point to the same object in memory, and you compare it with the == operator. Equality means that two objects logically have the same value, and you compare it with the equals() method. If you use == to compare strings or value objects, the result can be false even when the contents are the same.

Try these

Running with the default settings

  1. The old enum declaration listed ORDERED, PAID, SHIPPED in that order: Each enum constant is one object. PAID("Payment completed") is a Status object created by passing "Payment completed" to the constructor, and it is the only PAID object in the whole program. You cannot create another one with new.
  2. An order with the status PAID was saved to the database: An enum object cannot be stored as it is, so a value that stands for the constant is written instead. Here that value is PAID.ordinal(), which is 1. ordinal() returns the position of the constant in the declaration, counted from 0.
  3. After that, the enum declaration was changed: The new constant CANCELED was inserted between ORDERED and PAID. The ordinals are now ORDERED(0), CANCELED(1), PAID(2), SHIPPED(3). The value written in the database (1) is not updated and stays as it was.
  4. Status.values()[1] runs: values() returns an array that contains the constants in the order of the current declaration. The element at index 1 is CANCELED. The saved constant was PAID, but a different constant came back. There is no exception and no warning.
  5. System.out.println(s + " (" + s.label() + ")") runs: The output is "CANCELED (Order canceled)". label() is a method that you write yourself inside the enum, and it returns the label value that the constant received when it was created. An enum is also a class, so it can have fields and methods like this.
  6. s == Status.PAID runs: The result is false. There is only one object for each enum constant, so the same constant is the same object no matter how it was obtained. That is why you can compare with == instead of equals. The result is false not because of how the comparison is done, but because s really is a different constant (CANCELED). The program ends normally.

Result of this run

Code

enum Status {
    ORDERED("Order received"),
    CANCELED("Order canceled"),
    PAID("Payment completed"),
    SHIPPED("Shipping started");

    private final String label;

    Status(String label) {
        this.label = label;
    }

    String label() {
        return label;
    }
}

// The value of PAID.ordinal(), saved under the old declaration (order: ORDERED, PAID, SHIPPED)
int saved = 1;
Status s = Status.values()[saved];
System.out.println(s + " (" + s.label() + ")");
System.out.println(s == Status.PAID);

Assumptions of this model

All 45 combinations were checked by actually compiling and running the code shown on the screen (the enum declaration after the change and the code below it) on JDK 21 on this PC, and comparing the saved value, the two lines of output, the exit code, and the exception message. The saved values were obtained by actually getting ordinal(), name(), and code from the enum with the old declaration. In the code on the screen, the five lines below the enum declaration are inside the main method, and the declaration of main is omitted. The following were also checked in the same way: the same constant is the same object no matter how it is obtained, valueOf also throws an exception for a name that differs only in case, == does not throw an exception even when one side is null, and comparing constants of different enum classes with == is a compile-time error. No real database was used. The saved value was written directly in the code to imitate one. A change that removes a constant is not included in the settings. If a constant is removed, reading by ordinal returns a later constant that has moved up or throws an exception because the index is outside the array (ArrayIndexOutOfBoundsException), and reading by name makes valueOf throw an exception (this was also checked by running it separately).

Previous lab: Generics · Next lab: Annotations

Concepts in this lab

What this lab shows

Settings

Code


      
    

Diagram

Run comparison

Try these