Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 12

Enums: Beyond the Basics

Recommended resources

Hello! In our previous lesson, we established the crucial design principle of "programming to an interface." You refactored our notification system to be loosely coupled, where a NotificationManager could work with any NotificationService implementation. This gave our system flexibility in its behavior.

In this lesson, we'll introduce another powerful feature for writing robust and clean code: enumerations, or enums. While interfaces define a contract for what a class can do, enums define a fixed set of possible values a type can have. We'll explore how to create enums and, more importantly, how to empower them with their own fields, constructors, and methods, turning them from simple lists of names into powerful, self-contained objects. This is a fundamental tool for managing states, categories, and roles in the enterprise applications you aim to build.

Why Do We Need Enums?

Imagine you're building a system to track the status of a support ticket. You might be tempted to use String constants:

public static final String STATUS_OPEN = "OPEN";
public static final String STATUS_IN_PROGRESS = "IN_PROGRESS";
public static final String STATUS_CLOSED = "CLOSED";

This approach is fragile. What happens if someone makes a typo, like "in_progress"? The compiler won't catch it, and you'll have a runtime bug. What if they pass a completely invalid status like "PENDING"? Your code has to handle that possibility everywhere.

Enums solve this problem by creating a new type that can only hold one of the predefined values. This provides compile-time type safety, making your code more readable and far less error-prone.

For a quick and clear introduction to the concept, the following video explains what enums are and when to use them.

Java Enums Explained in 6 Minutes

The "Coding with John" channel provides a concise, 6-minute explanation of Java enums. This will give you a solid foundation on what they are and why they are useful.

Watch the first segment from the beginning to understand the basic concept and syntax for creating a simple enum. Continue watching from this section to see how to use enum values in your code and how to iterate over all possible values using the built-in values() method.

As the video demonstrated, using an enum like DaysOfTheWeek.THURSDAY is much safer and more descriptive than using a raw integer 4 or a string "Thursday".


Beyond Simple Constants: Enums as Classes

Here’s where enums become truly powerful. An enum in Java is not just a list of names; it's a special kind of class. This means it can have fields, constructors, and methods, just like any other class. This allows you to associate data and behavior directly with each constant.

Let's expand on our ticket status idea. Each status could have an associated color for the UI or a numeric code for database storage. An enum can encapsulate this information perfectly.

The Baeldung article, "A Guide to Java Enums," provides an excellent deep dive into this. We'll focus on the section that transforms a basic enum into a more complex one.

A Guide to Java Enums | Baeldung

This article is a comprehensive guide to enums. We will focus on the section that demonstrates how to add rich functionality to them.

Please read Section 5, Fields and Constructors. Pay close attention to how the PizzaStatus enum evolves from a simple list to a complex type with a timeToDelivery field, a constructor, and methods like isReady(). Notice the special syntax for defining the constants with their associated data: ORDERED(5).

As you read, you'll notice a few key rules about enum constructors:

  • They are implicitly private. You cannot use public or protected. This is because enums are designed to have a finite, fixed set of instances that you declare within the enum itself. You can't create new instances from outside code using new.
  • The constructor is called automatically once for each enum constant when the class is loaded.

This image clearly shows the compiler error you would get if you tried to declare a public constructor for an enum.

An IDE screenshot displaying a Java enum named 'Status' with a public constructor, which is flagged as an error with the message "Modifier 'public' not allowed here."

The following video from Telusko reinforces these concepts by building up a Laptop enum with a price field.

#70 Enum Class in Java

This video from Telusko provides another excellent walkthrough of adding state and behavior to enums, with a clear explanation of how constructors work.

First, watch how a price field and a parameterized constructor are added to the Laptop enum from this segment. Next, see how a getter method (getPrice()) is created to access this private field from this part. Finally, watch the explanation of enum constructors from this section. It clarifies why they are private and how they are invoked for each constant.

To see all these pieces together in a clean example, look at the code in the image below. It defines a TicketCategory enum where each constant has a description string. This is a very common and practical use case.

This Java code defines a `TicketCategory` enum with `CRITICAL`, `HIGH`, `MEDIUM`, and `LOW` constants. Each constant is initialized with a `description` string via a private constructor. The code also includes a `getDescription()` method and a `main` method demonstrating the use of the `ordinal()` method.

Built-in Enum Methods

The image above also shows the use of ordinal(). Because all enums implicitly extend the java.lang.Enum class, they inherit several useful methods:

  • values(): Returns an array containing all the enum constants in the order they are declared. You saw this in the "Coding with John" video.
  • name(): Returns the name of the enum constant as a String (e.g., "HIGH").
  • ordinal(): Returns the zero-based index of the constant in its declaration order (e.g., CRITICAL is 0, HIGH is 1).

A word of caution on ordinal(): While it seems convenient, relying on it can be brittle. If a developer reorders the constants in the enum definition, the ordinal values will change, which could break your application's logic in subtle ways. It's often more robust to define an explicit field (like id or code) in your enum, as we discussed earlier.

Connecting this to your goal of backend development, enums are used extensively. For example, in a Spring Boot application using Spring Security, you would almost certainly define user roles with an enum.

// A typical enum for user roles in a backend application
public enum ERole {
  USER,
  ADMIN,
  MODERATOR
}

This snippet is taken from the article "Implementing Role and Permission-Based Authorization in Spring Boot," which shows a real-world use case you'll encounter later. Using ERole.ADMIN is much safer and clearer than passing around the string "ADMIN".


Your Turn: Enhancing the Notification System

Let's apply this to our ongoing project. We'll add a priority level to our notifications using a feature-rich enum.

Task:

  1. Create a NotificationPriority Enum:

    • Create a new enum named NotificationPriority.
    • Define three constants: HIGH, NORMAL, and LOW.
  2. Add Fields and a Constructor:

    • Give the enum two private final fields: int level and String colorCode.
    • HIGH should have a level of 1 and a color code like "#FF0000" (Red).
    • NORMAL should have a level of 2 and a color code like "#000000" (Black).
    • LOW should have a level of 3 and a color code like "#808080" (Gray).
    • Create a private constructor that takes an int and a String to initialize these fields.
    • Update your constant declarations to call this constructor, e.g., HIGH(1, "#FF0000").
  3. Add Getter Methods:

    • Create public getter methods getLevel() and getColorCode() to expose the field values.
  4. Update the NotificationService Interface:

    • Change the signature of the send method in your NotificationService interface to:
      void send(String message, String recipient, NotificationPriority priority);
      
  5. Implement the Changes:

    • Update EmailService and SMSService to implement the modified send method. Inside the method, you can print the message along with the priority's level or color. For example:
      System.out.println("Sending Email with priority level: " + priority.getLevel());
      System.out.println("Recipient: " + recipient + ", Message: " + message);
      
    • Update your NotificationManager class so its doNotify method also accepts a NotificationPriority and passes it to the underlying notificationService.
  6. Test in Main:

    • In your main method, test the new functionality. Create NotificationManager instances and call doNotify with different priorities:
      System.out.println("\n--- Testing with Priorities ---");
      NotificationManager emailManager = new NotificationManager(new EmailService());
      
      // Send a high-priority email
      emailManager.doNotify("System alert: service is down!", "admin@example.com", NotificationPriority.HIGH);
      
      // Send a low-priority email
      emailManager.doNotify("Weekly newsletter is here.", "subscriber@example.com", NotificationPriority.LOW);
      

This exercise will solidify your understanding of how to create enums that carry both data and behavior, making your code more expressive and robust.


Conclusion

In this lesson, you've learned that enums are far more than just named constants. They are a powerful tool for creating type-safe, self-contained representations of fixed value sets. By equipping them with fields, constructors, and methods, you can directly associate data and logic with each constant, leading to cleaner and more maintainable code.

Key Takeaways:

  • Enums provide type safety for a fixed set of constants, preventing bugs from invalid or misspelled values.
  • An enum is a special type of class that can have fields, methods, and private constructors.
  • Each enum constant is an instance of the enum class, initialized via its constructor.
  • Enums are widely used in backend development to represent states, categories, roles, and more.

In our next lesson, we will take a closer look at the final keyword. You've already used it today to make your enum fields immutable, and next, we'll explore its application to classes, methods, and variables to control inheritance and prevent modification.