JAVA enumeration, more useful than you think!

I often find myself using enumerations in Java to represent a set of potential values for an object.

The ability to determine what values a type can have at compile time is a powerful ability that provides structure and meaning to your code.

When I first learned about enumerations, I thought they were just a tool for naming constants that could easily be replaced by the static constant string ENUM_VAL_NAME.

I found out later that I was wrong. As it turns out, Java enumerations have quite advanced features that make code clean, error-proof, and powerful.

Let’s take a look at some of the advanced enumeration features in Java and how you can leverage them to make your code simpler and more readable.

Enumerations are classes!

In Java, enumerations are a subclass of Object. Let’s look at the base class for all enumerations, Enum (modified for brevity).


public abstract class Enum<E extends Enum<E>>
    implements Constable.Comparable<E>, Serializable {
  private final String name;
  
  public final String name(a) {
      return name;
  }
  
  private final int ordinal;
  
  public final int ordinal(a) {
      return ordinal;
  }
  
  protected Enum(String name, int ordinal) {
      this.name = name;
      this.ordinal = ordinal;
  }
  
  public String toString(a) {
      return name;
  }
  
  public final boolean equals(Object other) {
      return this==other;
  }
  
  public final int hashCode(a) {
      return super.hashCode();
  }
  
  public final int compareTo(E o) { Enum<? > other = (Enum<? >)o; Enum<E> self =this;
      if(self.getClass() ! = other.getClass() &&// optimizationself.getDeclaringClass() ! = other.getDeclaringClass())throw new ClassCastException();
      returnself.ordinal - other.ordinal; }}Copy the code

As you can see, this is basically just a regular abstract class with two fields, name and ordinal.

So enumerations are classes, so they have many of the features of regular classes.

We can provide instance methods, constructors, and fields for enumerations. We can override toString (), but not hashCode () or equals (Object Other).

Let’s take a look at our enumeration example, Operation

  enum Operation {
    ADD,
    SUBTRACT,
    MULTIPLY
  }

Copy the code

This enumeration indicates that an Operation can be performed on two values and will produce a result. Your initial idea of how to do this might be to use a switch statement, as follows:

  public int apply(Operation operation, int arg1, int arg2) {
    switch(operation) {
      case ADD:
        return arg1 + arg2;
      case SUBTRACT:
        return arg1 - arg2;
      case MULTIPLY:
        return arg1 * arg2;
      default:
        throw newUnsupportedOperationException(); }}Copy the code

Of course, there are problems with this.

The first problem is that if we add a new Operation to our enumeration Operation, the compiler will not tell us that the switch cannot handle the new Operation correctly.

Worse, if a lazy developer copies or rewrites this code in another class, we might not be able to update it.

The second problem is that the default case, default, is required in every program, although we know it will never happen in the correct code.

This is because the Java compiler knows about the first problem above and wants to make sure that we can handle adding a new enumeration to Operation without our knowledge.

Fortunately, Java8 provides a clean solution with functional programming.

Function enumeration implementation

Because enumerations are classes, we can create an enumeration field to hold the function that performs the operation.

But before we find a solution, let’s take a look at some refactoring.

First, let’s put the switch in the enum class.

  
enum Operation {
  ADD,
  SUBTRACT,
  MULTIPLY;
  
  public static int apply(Operation operation, int arg1, int arg2) {
    switch(operation) {
      case ADD:
        return arg1 + arg2;
      case SUBTRACT:
        return arg1 - arg2;
      case MULTIPLY:
        return arg1 * arg2;
      default:
        throw newUnsupportedOperationException(); }}}Copy the code

Operation. Apply (Operation.ADD, 2, 3);

Because we are now calling the method from Operation, we can change it to an instance method and use this instead of operation.apply (), as follows:

public int apply(int arg1, int arg2) {
  switch(this) {
    case ADD:
      return arg1 + arg2;
    case SUBTRACT:
      return arg1 - arg2;
    case MULTIPLY:
      return arg1 * arg2;
    default:
      throw newUnsupportedOperationException(); }}Copy the code

Use it like this: operation.add.apply (2, 3);

It looks better. Now let’s go one step further and eliminate switch statements completely by using functional programming.

enum Operation {
              ADD((x, y) -> x + y),
              SUBTRACT((x, y) -> x - y),
              MULTIPLY((x, y) -> x * y);
  
              Operation(BiFunction<Integer, Integer, Integer> operation) {
                      this.operation = operation;
              }
  
              private final BiFunction<Integer, Integer, Integer> operation;
  
              public int apply(int x, int y) {
                      returnoperation.apply(x, y); }}Copy the code

Here’s what I did:

  • A field BiFunction

    operation is added
    ,>
  • The constructor for Operation was created with BiFunction.
  • Call the constructor in the enumeration definition and specify BiFunction

    with a lambda.
    ,>

The Java. Util. Function. BiFunction operation field is the function with two parameters ((method) of references.

In our case, both arguments are int and the return value is int. Unfortunately, Java parameterized types do not support primitives, so we must use Integer.

Since BiFunction is annotated with @functionInterface, we can define one using Lambda notation.

Since our function takes two arguments, we can specify them using (x, y).

We then define a single-line method that returns a value using ->x+y. This is equivalent to the following method, only more concise.

  class Adder implements BiFunction<Integer.Integer.Integer> {
          @Override
          public Integer apply(Integer x, Integer y) {
                  returnx + y; }}Copy the code

Our new Operation implementation takes the same approach: operation.add.apply (2, 3); .

However, this implementation is better because the compiler tells us when a new Operation has been added, which requires us to update the new function. If not this, if we still don’t remember when adding new Operation update switch statement, it is possible to get UnsupportedOperationException ().

The key points

  • Enum Enum is an extension of Enum.

  • Enum Enumerations can have fields, constructors, and instance methods.

  • Enum Enum fields can store functions. Used in conjunction with lambdas, it is possible to create clean, safe enumeration-specific function implementations and enforce them at compile time (instead of using switch).

Here is the GitHub address for this example. (github.com/alex-power/…

Reference for this article: medium.com/javarevisit…

Welcome to pay attention to my public account: Program monkey DD, get the exclusive arrangement of learning resources, daily dry goods and welfare gifts.