Using enumeration in Java well, it's not so simple!

Keywords: Programming Java JSON Spring jvm

Recently, I revisited Java enumeration and saw this article that I thought was good, so I simply translated and improved some content and shared it with you. I hope you can also get something. In addition, don't forget that there is a supplement at the end of the article!

ps: here is an enumeration article, which is also a very practical practice about spring boot global exception handling, which uses enumeration.

This article is translated by JavaGuide, public address: JavaGuide, original address: https://www.baeldung.com/a-guide-to-java-enums .

Reprint please indicate the above paragraph.

1. overview

In this article, we will see what are Java enumerations, what problems they solve, and how to implement some design patterns using java enumerations in practice.

The enum keyword is introduced into java5 to represent a special type of class, which always inherits the java.lang.Enum class. More content can be viewed by yourself Official documents.

Enumerations are often compared with constants, perhaps because we use enumerations to replace constants. So what are the advantages of this approach?

Constants defined in this way make code more readable, allow compile time checks, pre record a list of acceptable values, and avoid unexpected behavior caused by passing in invalid values.

The following example defines the status of a simple enumeration type pizza order. There are three statuses: ordered, ready, and delivered:

package shuang.kou.enumdemo.enumtest;

public enum PizzaStatus {
    ORDERED,
    READY, 
    DELIVERED; 
}

In short, we avoid defining constants through the above code. We put all the constants of order status and pizza order status into an enumeration type.

System.out.println(PizzaStatus.ORDERED.name());//ORDERED
System.out.println(PizzaStatus.ORDERED);//ORDERED
System.out.println(PizzaStatus.ORDERED.name().getClass());//class java.lang.String
System.out.println(PizzaStatus.ORDERED.getClass());//class shuang.kou.enumdemo.enumtest.PizzaStatus

2. Custom enumeration method

Now that we have a basic understanding of what enumerations are and how to use them, let's take the previous example to a new level by defining some additional API methods on enumerations:

public class Pizza {
    private PizzaStatus status;
    public enum PizzaStatus {
        ORDERED,
        READY,
        DELIVERED;
    }
 
    public boolean isDeliverable() {
        if (getStatus() == PizzaStatus.READY) {
            return true;
        }
        return false;
    }
     
    // Methods that set and get the status variable.
}

3. Use = = to compare enumeration types

Because enumeration types ensure that only one constant instance exists in the JVM, we can safely use the "= =" operator to compare two variables, as shown in the above example; in addition, the "= =" operator provides compile time and runtime security.

First, let's look at runtime security in the following code snippet, where the '= =' operator is used to compare States and NullPointerException is not raised if both values are null. Instead, if you use the equals method, a NullPointerException is thrown:

if(testPz.getStatus().equals(Pizza.PizzaStatus.DELIVERED)); 
if(testPz.getStatus() == Pizza.PizzaStatus.DELIVERED); 

For compile time security, let's look at another example, where two different enumeration types are compared, and the comparison result using the equal method is determined to be true, because the enumeration value of the getStatus method is consistent with the enumeration value of the other type, but it should be false logically. This problem can be avoided by using the = = operator. Because the compiler indicates a type incompatibility error:

if(testPz.getStatus().equals(TestColor.GREEN));
if(testPz.getStatus() == TestColor.GREEN);

4. Use enumeration type in switch statement

public int getDeliveryTimeInDays() {
    switch (status) {
        case ORDERED: return 5;
        case READY: return 2;
        case DELIVERED: return 0;
    }
    return 0;
}

5. Enumeration type properties, methods and constructors

At the end of the article, I have my (Java Guide) supplement.

You can make it more powerful by defining properties, methods, and constructors in enumeration types.

Next, let's extend the above example to realize the transition from one phase of Pisa to another, and learn how to get rid of the if statement and switch statement used before:

public class Pizza {
 
    private PizzaStatus status;
    public enum PizzaStatus {
        ORDERED (5){
            @Override
            public boolean isOrdered() {
                return true;
            }
        },
        READY (2){
            @Override
            public boolean isReady() {
                return true;
            }
        },
        DELIVERED (0){
            @Override
            public boolean isDelivered() {
                return true;
            }
        };
 
        private int timeToDelivery;
 
        public boolean isOrdered() {return false;}
 
        public boolean isReady() {return false;}
 
        public boolean isDelivered(){return false;}
 
        public int getTimeToDelivery() {
            return timeToDelivery;
        }
 
        PizzaStatus (int timeToDelivery) {
            this.timeToDelivery = timeToDelivery;
        }
    }
 
    public boolean isDeliverable() {
        return this.status.isReady();
    }
 
    public void printTimeToDeliver() {
        System.out.println("Time to delivery is " + 
          this.getStatus().getTimeToDelivery());
    }
     
    // Methods that set and get the status variable.
}

The following code shows how it work s:

@Test
public void givenPizaOrder_whenReady_thenDeliverable() {
    Pizza testPz = new Pizza();
    testPz.setStatus(Pizza.PizzaStatus.READY);
    assertTrue(testPz.isDeliverable());
}

6.EnumSet and EnumMap

6.1. EnumSet

EnumSet is a Set type designed specifically for enumeration types.

Compared with HashSet, it is a very efficient and compact representation of a specific Enum constant set because of the use of internal bit vector representation.

It provides a type safe alternative to the traditional int based "bit flags", enabling us to write simpler code that is easier to read and maintain.

EnumSet is an abstract class, which has two implementations: RegularEnumSet and JumboEnumSet. The choice depends on the number of constants in the enumeration at the time of instantiation.

EnumSet is suitable for enumerating constant set operations (such as subset, add, delete, containsAll and removeAll batch operations) in many scenarios; Enum.values() is used if you need to iterate over all possible constants.

public class Pizza {
 
    private static EnumSet<PizzaStatus> undeliveredPizzaStatuses =
      EnumSet.of(PizzaStatus.ORDERED, PizzaStatus.READY);
 
    private PizzaStatus status;
 
    public enum PizzaStatus {
        ...
    }
 
    public boolean isDeliverable() {
        return this.status.isReady();
    }
 
    public void printTimeToDeliver() {
        System.out.println("Time to delivery is " + 
          this.getStatus().getTimeToDelivery() + " days");
    }
 
    public static List<Pizza> getAllUndeliveredPizzas(List<Pizza> input) {
        return input.stream().filter(
          (s) -> undeliveredPizzaStatuses.contains(s.getStatus()))
            .collect(Collectors.toList());
    }
 
    public void deliver() { 
        if (isDeliverable()) { 
            PizzaDeliverySystemConfiguration.getInstance().getDeliveryStrategy()
              .deliver(this); 
            this.setStatus(PizzaStatus.DELIVERED); 
        } 
    }
     
    // Methods that set and get the status variable.
}

The following tests demonstrate the power of EnumSet in some scenarios:

@Test
public void givenPizaOrders_whenRetrievingUnDeliveredPzs_thenCorrectlyRetrieved() {
    List<Pizza> pzList = new ArrayList<>();
    Pizza pz1 = new Pizza();
    pz1.setStatus(Pizza.PizzaStatus.DELIVERED);
 
    Pizza pz2 = new Pizza();
    pz2.setStatus(Pizza.PizzaStatus.ORDERED);
 
    Pizza pz3 = new Pizza();
    pz3.setStatus(Pizza.PizzaStatus.ORDERED);
 
    Pizza pz4 = new Pizza();
    pz4.setStatus(Pizza.PizzaStatus.READY);
 
    pzList.add(pz1);
    pzList.add(pz2);
    pzList.add(pz3);
    pzList.add(pz4);
 
    List<Pizza> undeliveredPzs = Pizza.getAllUndeliveredPizzas(pzList); 
    assertTrue(undeliveredPzs.size() == 3); 
}

6.2. EnumMap

EnumMap is a specialized mapping implementation that uses enumeration constants as keys. Compared with the corresponding HashMap, it is an efficient and compact implementation, and is internally represented as an array:

EnumMap<Pizza.PizzaStatus, Pizza> map;

Let's take a quick look at a real-world example of how to use it in practice:

public static EnumMap<PizzaStatus, List<Pizza>> 
  groupPizzaByStatus(List<Pizza> pizzaList) {
    EnumMap<PizzaStatus, List<Pizza>> pzByStatus = 
      new EnumMap<PizzaStatus, List<Pizza>>(PizzaStatus.class);
     
    for (Pizza pz : pizzaList) {
        PizzaStatus status = pz.getStatus();
        if (pzByStatus.containsKey(status)) {
            pzByStatus.get(status).add(pz);
        } else {
            List<Pizza> newPzList = new ArrayList<Pizza>();
            newPzList.add(pz);
            pzByStatus.put(status, newPzList);
        }
    }
    return pzByStatus;
}

The following tests demonstrate the power of EnumMap in some scenarios:

@Test
public void givenPizaOrders_whenGroupByStatusCalled_thenCorrectlyGrouped() {
    List<Pizza> pzList = new ArrayList<>();
    Pizza pz1 = new Pizza();
    pz1.setStatus(Pizza.PizzaStatus.DELIVERED);
 
    Pizza pz2 = new Pizza();
    pz2.setStatus(Pizza.PizzaStatus.ORDERED);
 
    Pizza pz3 = new Pizza();
    pz3.setStatus(Pizza.PizzaStatus.ORDERED);
 
    Pizza pz4 = new Pizza();
    pz4.setStatus(Pizza.PizzaStatus.READY);
 
    pzList.add(pz1);
    pzList.add(pz2);
    pzList.add(pz3);
    pzList.add(pz4);
 
    EnumMap<Pizza.PizzaStatus,List<Pizza>> map = Pizza.groupPizzaByStatus(pzList);
    assertTrue(map.get(Pizza.PizzaStatus.DELIVERED).size() == 1);
    assertTrue(map.get(Pizza.PizzaStatus.ORDERED).size() == 2);
    assertTrue(map.get(Pizza.PizzaStatus.READY).size() == 1);
}

7. Implement some design patterns by enumeration

7.1 single case mode

In general, using classes to implement Singleton pattern is not easy. Enumeration provides a simple way to implement Singleton pattern.

This method is highly recommended by Effective Java and Java and patterns. What are the benefits of using this method to implement enumeration?

<Effective Java>

This method is similar to the public domain method in function, but it is more concise and provides a serialization mechanism free of charge, which absolutely prevents multiple instantiations, even in the face of complex serialization or reflection attacks. Although this method has not been widely used, the enumeration type of single element has become the best way to implement Singleton. - "Effective Java Chinese version 2"

Java and patterns

In Java and patterns, the author wrote that using enumeration to achieve single instance control would be more concise, and provide a serialization mechanism free of charge, and be fundamentally guaranteed by the JVM, absolutely prevent multiple instantiations, which is a more concise, efficient and safe way to implement a single instance.

The following code snippet shows how to use enumeration to implement singleton mode:

public enum PizzaDeliverySystemConfiguration {
    INSTANCE;
    PizzaDeliverySystemConfiguration() {
        // Initialization configuration which involves
        // overriding defaults like delivery strategy
    }
 
    private PizzaDeliveryStrategy deliveryStrategy = PizzaDeliveryStrategy.NORMAL;
 
    public static PizzaDeliverySystemConfiguration getInstance() {
        return INSTANCE;
    }
 
    public PizzaDeliveryStrategy getDeliveryStrategy() {
        return deliveryStrategy;
    }
}

How to use it? Look at the following code:

PizzaDeliveryStrategy deliveryStrategy = PizzaDeliverySystemConfiguration.getInstance().getDeliveryStrategy();

Through PizzaDeliverySystemConfiguration.getInstance(), the single PizzaDeliverySystemConfiguration is obtained

7.2 strategic model

In general, policy patterns are implemented by different classes implementing the same interface.

This means adding a new policy means adding a new implementation class. With enumeration, you can easily accomplish this task, adding a new implementation means only defining another instance with one implementation.

The following code snippet shows how to implement the policy pattern using enumeration:

public enum PizzaDeliveryStrategy {
    EXPRESS {
        @Override
        public void deliver(Pizza pz) {
            System.out.println("Pizza will be delivered in express mode");
        }
    },
    NORMAL {
        @Override
        public void deliver(Pizza pz) {
            System.out.println("Pizza will be delivered in normal mode");
        }
    };
 
    public abstract void deliver(Pizza pz);
}

Add the following methods to Pizza:

public void deliver() {
    if (isDeliverable()) {
        PizzaDeliverySystemConfiguration.getInstance().getDeliveryStrategy()
          .deliver(this);
        this.setStatus(PizzaStatus.DELIVERED);
    }
}

How to use it? Look at the following code:

@Test
public void givenPizaOrder_whenDelivered_thenPizzaGetsDeliveredAndStatusChanges() {
    Pizza pz = new Pizza();
    pz.setStatus(Pizza.PizzaStatus.READY);
    pz.deliver();
    assertTrue(pz.getStatus() == Pizza.PizzaStatus.DELIVERED);
}

8. Java 8 and enumeration

The Pizza class can be rewritten in Java 8. You can see how the method lambda and Stream API make the getallundeliveredpizza() and grouppizza bystatus() methods so concise:

getAllUndeliveredPizzas():

public static List<Pizza> getAllUndeliveredPizzas(List<Pizza> input) {
    return input.stream().filter(
      (s) -> !deliveredPizzaStatuses.contains(s.getStatus()))
        .collect(Collectors.toList());
}

groupPizzaByStatus() :

public static EnumMap<PizzaStatus, List<Pizza>> 
  groupPizzaByStatus(List<Pizza> pzList) {
    EnumMap<PizzaStatus, List<Pizza>> map = pzList.stream().collect(
      Collectors.groupingBy(Pizza::getStatus,
      () -> new EnumMap<>(PizzaStatus.class), Collectors.toList()));
    return map;
}

9. JSON representation of enum type

With the Jackson library, you can represent JSON of enumeration type as POJO. The following code snippet shows the Jackson annotations that can be used for the same purpose:

@JsonFormat(shape = JsonFormat.Shape.OBJECT)
public enum PizzaStatus {
    ORDERED (5){
        @Override
        public boolean isOrdered() {
            return true;
        }
    },
    READY (2){
        @Override
        public boolean isReady() {
            return true;
        }
    },
    DELIVERED (0){
        @Override
        public boolean isDelivered() {
            return true;
        }
    };
 
    private int timeToDelivery;
 
    public boolean isOrdered() {return false;}
 
    public boolean isReady() {return false;}
 
    public boolean isDelivered(){return false;}
 
    @JsonProperty("timeToDelivery")
    public int getTimeToDelivery() {
        return timeToDelivery;
    }
 
    private PizzaStatus (int timeToDelivery) {
        this.timeToDelivery = timeToDelivery;
    }
}

We can use Pizza and Pizza status as follows:

Pizza pz = new Pizza();
pz.setStatus(Pizza.PizzaStatus.READY);
System.out.println(Pizza.getJsonString(pz));

Generate the Pizza state as shown in the following JSON:

{
  "status" : {
    "timeToDelivery" : 2,
    "ready" : true,
    "ordered" : false,
    "delivered" : false
  },
  "deliverable" : true
}

For more information about JSON serialization / deserialization (including customization) of enumerated types, see Jackson - serializes the enumeration as a JSON object.

10. summary

In this paper, we discuss Java enumeration types, from basic knowledge to advanced applications and practical application scenarios, let us feel the powerful function of enumeration.

11. supplement

As we mentioned above, we can make it more powerful by defining properties, methods, and constructors in enumeration types.

Let me show you a practical example. When we call SMS verification code, there may be several different uses. We define it as follows:


public enum PinType {

    REGISTER(100000, "Registered use"),
    FORGET_PASSWORD(100001, "Forget password usage"),
    UPDATE_PHONE_NUMBER(100002, "Update mobile number usage");

    private final int code;
    private final String message;

    PinType(int code, String message) {
        this.code = code;
        this.message = message;
    }

    public int getCode() {
        return code;
    }

    public String getMessage() {
        return message;
    }

    @Override
    public String toString() {
        return "PinType{" +
                "code=" + code +
                ", message='" + message + '\'' +
                '}';
    }
}

Actual use:

System.out.println(PinType.FORGET_PASSWORD.getCode());
System.out.println(PinType.FORGET_PASSWORD.getMessage());
System.out.println(PinType.FORGET_PASSWORD.toString());

Output:

100001
 Forget password usage
 PinType{code=100001, message = forget password use '}

In this way, it will be very flexible and convenient in practical use!

Open source project recommendation

Other open source projects recommended by the author:

  1. JavaGuide [java learning + interview guide] covers the core knowledge that most Java programmers need to master.
  2. springboot-guide : Spring Boot tutorial for novice and experienced developers (in spare time maintenance, welcome to maintenance together).
  3. programmer-advancement I think technicians should have some good habits!
  4. spring-security-jwt-guide : start from scratch! Spring Security With JWT (including permission verification) backend part code.

official account

Posted by habib009pk on Wed, 29 Jan 2020 00:19:07 -0800