Advanced Enums in Java
Tags: java, programming, tutorial
Published: 2024-02-17 03:03:21

Enums are a powerful mechanism used to specify that a type is limited to one of a set of constants. In addition to simple labeling of constants, the Java implementation of Enums can be used for simplifying code and improving readability. This article will explore some advanced uses of Java Enums, including advanced type safety, defining configuration, singletons, and defining simple state machines as a directed, acyclic graph (DAG). 

## Type Safety With Constants
The first and often only use of Enums taught to Java developers is to use it to label constant values. Not only is this a useful tool for readability by avoiding "magic constants", it is also a powerful tool for defensive programming by adding type constraints to method parameters. 

Consider the following function that adds an application-specific error type to the general log message for when an exception occurs. 

```java
public void logError(String errorType, Exception e){ 
  log.error(String.format("Application error: %s",errorType), e);
}
// ... 
public void createAccount(){ 
  try{ 
    // ...
  } catch (Exception e){
    logError("Account Creation", e);
  }
} 
```

This code has a few issues that make it difficult to maintain 
1. **Repetition** if there are multiple places that log errors during account creation, we have to hope the developer knows that the correct label for the error logs is "Account Creation" and not "creating account" or "setup" or something else. This repetition is also susceptible to misspellings or inconsistent capitalization that causes issues with searching debug logs.  
2. **Type Safety** Yes, the parameter has to be a `string`, but it's not likely that _any_ `string` is a valid input to this method. Instead, there needs to be some mechanism to limit what `string` is acceptable as input. 

We can leverage Enums to address these problems 
```java
// AppErrorType.java
public enum AppErrorType { 
  ACCOUNT_CREATION("Account Creation"),
  PROFILE_SETUP("Profile Setup"),
  //...
  EMAIL_UPDATE("Email Update");
  
  final String label; 
  ErrorType(final String l) { 
    this.label = l; 
  } 
}

// some other class...
public void logError(AppErrorType errorType, Exception e){ 
  final String msg = String.format("Application error: %s",
                                    errorType.label);
  log.error(msg, e);
}
// ... 
public void createAccount(){ 
  try{ 
    // ...
  } catch (Exception e){
    logError(AppErrorType.ACCOUNT_CREATION, e);
  }
} 
```
Using an Enum for error types, this update 
- eliminates the chances of misspellings in the error labels 
- limits the scope of valid `string` instead of allowing any arbitrary `string` input to the `logError` method
- eliminates the need for extra logic and tests on the method to ignore invalid `string`s
- provides a single place for developers to look up what error labels are valid. 

This technique can be implemented for any data type, and is a valuable tool for defensive programming where a method needs to only accept a particular set of valid values as an input.

## Defining Configuration 
Additionally, since Enums can have any number of fields, they are useful for configuring and grouping related values. 
 
```java
enum Subscription { 
  BASIC(9.99, 
        1,
        List.of(Feature.ADS,
                Feature.BASIC_CHANNELS)),
  PREMIUM(21.00,
          4,
          List.of(Feature.AD_FREE,
                  Feature.BASIC_CHANNELS,
                  Feature.SPORTS_PACKAGE,
                  Feature.PREMIUM_MOVIES));
  private double cost;
  private int maxDevices;
  private List<Feature> includedFeatures;

  Subscription(Double c, int d, List<Feature> f){
    this.cost = c;
    this.maxDevices = d;
    this.includedFeatures = f;
  } 
}
```

## Implementing Singletons 
A singleton is a class that is intended to only ever have a single instance created. Seemingly simple on the surface, implementing a thread-safe, serializable singleton in Java has several approaches that are tricky and have performance implications in multithreaded environments. 
```java
// singleton with double-checked locking
public class MySingleton {
  private static volatile MySingleton instance;
  public static MySingleton getInstance() {
    if (instance == null) {
      synchronized (MySingleton .class) {
        if (instance == null) {
          instance = new MySingleton();
        }
      }
    }
    return instance;
  }
  void doSomething(){
    //...
  }
//...
} 
```
Instead, we can leverage the fact that the Java Virtual Machine(JVM) guarantees `Enum`s to only ever have one instance created. 
```java
public enum MySingleton { 
  INSTANCE;
  
  void doSomething(){
    //...
  }
  //...
}
```
This is a simple way to leverage the JVM to ensure our singletons are thread-safe and serializable without having to repeat the same boilerplate construction and serialization logic for every singleton we create. 

## State Machines 
Many applications in software can be modeled as workflows. A workflow is a directed graph of states that can be implemented as a Finite State Machine(FSM). The FSM specifies all valid transitions from any given state. An input, called a `context`, enters an initial state and can traverse the graph after some processing until it reaches a terminal state (a state which has no valid transitions to other states). 

Since Enums are classes in Java, we can use Enums to model the finite state machine with the following steps:
1. define an interface for the state machine states that allows a state process a `context` and indicate whether the current state is `terminal`
```java 
interface FSMState {
  abstract FSMState process(Context ctx);
  abstract boolean isTerminal();
}
```
2. Implement the states using an interface. the `process` method on each state performs whatever processing is required on the context at that state and then returns the next state to transition to in the graph. 
```java
enum State implements FSMState{ 
  FIRST { 
    public State process(Context ctx){
      return SECOND;
    }

    public boolean isTerminal(){
      return false;
    }
  },
  SECOND { 
    public State process(Context ctx){
      return LAST;
    } 

    public boolean isTerminal(){
      return false;
    }
  },
  LAST {
    public State process(Context ctx){
      return this;
    }

    public boolean isTerminal(){
      return true;
    }
  };
}
```
3. Define the `context` object that will be the input throughout the state machine. A context object must contain it's own current `state`, and implement a `process` function that advances the context by using the `state`. This will be driven by an external `machine` later. 

```java
class Context {  
  private FSMState currentState;

  public Context(){ 
    this.currentState = State.FIRST;
  }

  public FSMState getCurrentState(){
    return this.currentState;
  }

  public void process(){ 
    if(!this.currentState.isTerminal()){
      this.currentState = this.currentState.process(this);
    }
  }

  public boolean isFinished(){ 
    return this.currentState.isTerminal();
  }
} 
```
4. Define a `machine` that initializes and drives the context through it's different states. The machine logic is really dependent on the application, but an example of how to drive a context through states looks something like 
```java
class Machine { 
  public static void main(String[] args) {
    Context context = new Context();
    System.out.println("current state: "+context.getCurrentState());
    while(!context.isFinished()){ 
      context.process();
      System.out.println("current state: "+context.getCurrentState());
    }
  }
}
```
Given our examples, running `Machine.main()` results in the following output 
```
current state: FIRST
current state: SECOND
current state: LAST
```

## A Powerful Addition to the Toolbox
Enums are a powerful data type, especially in Java. This article has demonstrated how Enums can be used to improve readability of code by labeling constants, restricting method inputs, and reduce boilerplate when defining Singletons and state machines. Enums are a valuable addition to any developer's mental toolbox.