Summarize the use of enumerations in work

1. Overview

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

The enum keyword was introduced in java5 to indicate a special type of class, which always inherits the java.lang.Enum class, and you can check its official documentation for more content.

Enumerations are often compared with constants, probably because we actually use enumerations in large numbers to replace constants. So what are the advantages of this approach?

Constants defined in this way make the code more readable, allow compile-time checks, pre-record a list of acceptable values, and avoid unexpected behavior due to invalid values ​​being passed in.

The following example defines the status of a simple enumerated type pizza order. There are three states of ORDERED, READY, and DELIVERED:

package shuang.kou.enumdemo.enumtest;

publicenum PizzaStatus {
    ORDERED,
    READY,
    DELIVERED;
}

To put it simply, we avoid defining constants through the above code. We put all constants related to the status of the pizza order into an enumerated 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 enums are and how to use them, let us take the previous example to a new level by defining some additional API methods on enums:

publicclass Pizza {
    private PizzaStatus status;
    publicenum PizzaStatus {
        ORDERED,
        READY,
        DELIVERED;
    }
 
    public boolean isDeliverable() {
        if (getStatus() == PizzaStatus.READY) {
            returntrue;
        }
        returnfalse;
    }
     
    // Methods that set and get the status variable.
}

3. Use == to compare enumerated types

Since the enumeration type ensures that there is only one constant instance in the JVM, we can safely use the "==" operator to compare two variables, as shown in the above example; in addition, the "==" operator can provide compile-time and runtime Security.

First, let's take a look at the runtime security in the following code snippet, where the "==" operator is used to compare states, and if both values ​​are null, NullPointerException will not be thrown. On the contrary, if the equals method is used, a NullPointerException will be thrown:

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

For compile-time safety, let’s look at another example. Two different enumeration types are compared. The result of the comparison using the equal method is determined to be true, because the enumeration value of the getStatus method is consistent with another type enumeration value, but logically Is false. This problem can be avoided by using the == operator. Because the compiler will indicate a type incompatibility error:

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

4. Use enumerated types in switch statements

public int getDeliveryTimeInDays() {
    switch (status) {
        case ORDERED: return5;
        case READY: return2;
        case DELIVERED: return0;
    }
    return0;
}

5. Attributes, methods and constructors of enumeration types

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

Next, let us extend the above example to realize the transition from one stage of pizza to another, and learn how to get rid of the if statement and switch statement used before:

publicclass Pizza {
 
    private PizzaStatus status;
    publicenum PizzaStatus {
        ORDERED (5){
            @Override
            public boolean isOrdered() {
                returntrue;
            }
        },
        READY (2){
            @Override
            public boolean isReady() {
                returntrue;
            }
        },
        DELIVERED (0){
            @Override
            public boolean isDelivered() {
                returntrue;
            }
        };
 
        privateint timeToDelivery;
 
        public boolean isOrdered() {returnfalse;}
 
        public boolean isReady() {returnfalse;}
 
        public boolean isDelivered(){returnfalse;}
 
        public int getTimeToDelivery() {
            return timeToDelivery;
        }
 
        PizzaStatus (int timeToDelivery) {
            this.timeToDelivery = timeToDelivery;
        }
    }
 
    public boolean isDeliverable() {
        returnthis.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 works:

@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 It is a type specially designed for enumerated  Set types.

In HashSetcontrast, due to the use of internal bit vector representation, it is a Enum very efficient and compact representation of a specific  set of constants.

It provides a type-safe alternative to traditional int-based "bit flags", allowing us to write concise code that is more readable and easy to maintain.

EnumSet Is an abstract class, which has two implementations: RegularEnumSet , , JumboEnumSetwhich depends upon the number of instances of the enumeration constants selection.

In many scenarios, the use of enumeration constant collection operations (such as: subsetting, adding, deleting, containsAlland removeAllbatch operations) EnumSetis very suitable; if you need to iterate all possible constants, use it Enum.values().

publicclass Pizza {
 
    privatestatic EnumSet<PizzaStatus> undeliveredPizzaStatuses =
      EnumSet.of(PizzaStatus.ORDERED, PizzaStatus.READY);
 
    private PizzaStatus status;
 
    publicenum PizzaStatus {
        ...
    }
 
    public boolean isDeliverable() {
        returnthis.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 test demonstrates the  EnumSet powerful features in certain 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

EnumMapIs a specialized mapping implementation that uses enum constants as keys. Compared with its  HashMap counterpart, it is an efficient and compact implementation, and is represented as an array internally:

EnumMap<Pizza.PizzaStatus, Pizza> map;

Let's take a quick look at a real example that demonstrates how to use it in practice:

publicstatic 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 test demonstrates the  EnumMap powerful features in certain 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 through enumeration

7.1 Singleton mode

Generally, it is not easy to implement the Singleton pattern with a class, and enumeration provides an easy way to implement a singleton.

"Effective Java" and "Java and Patterns" both highly recommend this method. What are the benefits of using this method to implement enumeration?

《Effective Java》

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

"Java and Patterns"

In "Java and Patterns", the author wrote that the use of enumeration to achieve single-instance control will be more concise, and serialization mechanism is provided free of charge, and the JVM fundamentally provides guarantee to absolutely prevent multiple instantiations. A more concise, efficient and safe way to implement singletons.

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

publicenum 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? Please look at the following code:

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

What is  PizzaDeliverySystemConfiguration.getInstance() obtained through is singleton PizzaDeliverySystemConfiguration

7.2 Strategy Mode

Generally, the strategy pattern is implemented by different classes implementing the same interface.

This also means that adding a new strategy means adding a new implementation class. Using enumerations, this task can be done easily, adding a new implementation means only defining another instance with a certain implementation.

The following code snippet shows how to use enumeration to implement the strategy pattern:

publicenum 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);
}

To  Pizzaadd the following method:

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

How to use it? Please 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 methods lambda and Stream API make the  getAllUndeliveredPizzas()and groupPizzaByStatus()method so concise:

getAllUndeliveredPizzas():

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

groupPizzaByStatus() :

publicstatic 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

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

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

We can use Pizza and  as follows  PizzaStatus:

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

The generated Pizza status is displayed in the following JSON:

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

For more information on JSON serialization/deserialization (including customization) of enumeration types, see Jackson-Serialize Enumerations into JSON Objects.

10. Summary

In this article, we discussed the Java enumeration types, from basic knowledge to advanced applications and practical application scenarios, let us feel the power of enumeration.

11. Supplement

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

Let me show you through an actual example. When we call the SMS verification code, there may be several different purposes. We define it as follows:

publicenum PinType {

    REGISTER(100000, "注册使用"),
    FORGET_PASSWORD(100001, "忘记密码使用"),
    UPDATE_PHONE_NUMBER(100002, "更新手机号码使用");

    privatefinalint code;
    privatefinal 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
忘记密码使用
PinType{code=100001, message='忘记密码使用'}

In this case, it will be very flexible and convenient in actual use!

Guess you like

Origin blog.csdn.net/qq_39809613/article/details/108010204