》最近重读Java枚举,看到这篇文章觉得还不错,所以简单翻译改进了一些内容,分享给大家,希望大家也能有所收获。另外,不要别忘了还有补充!ps:这里贴了一篇枚举的文章,也是因为在betterpractice中有一篇关于SpringBoot全局异常处理的非常实用的文章用到了枚举。1.概述在本文中,我们将了解什么是Java枚举,它们解决了哪些问题,以及如何在实践中使用Java枚举来实现一些设计模式。java5中引入了enum关键字,表示一种特殊类型的类,总是继承java.lang.Enum类。有关更多信息,您可以查看其官方文档。枚举经常和常量进行比较,可能是因为我们实际使用枚举的很多地方都是为了代替常量。那么这种方法的优点是什么?以这种方式定义的常量使代码更具可读性,允许编译时检查,预先记录可接受值列表,并避免由于传递无效值而导致的意外行为。以下示例定义了一个简单枚举类型比萨饼订单的状态。有ORDERED、READY、DELIVERED三种状态:packageshuang.kou.enumdemo.enumtest;publicenumPizzaStatus{ORDERED,READY,DELIVERED;}总之,我们通过上面的代码避免定义常量,我们将所有与披萨订单状态相关的常量放入一个枚举类型中。System.out.println(PizzaStatus.ORDERED.name());//ORDEREDSystem.out.println(PizzaStatus.ORDERED);//ORDEREDSystem.out.println(PizzaStatus.ORDERED.name().getClass());//classjava.lang.StringSystem.out.println(PizzaStatus.ORDERED.getClass());//classshuang.kou.enumdemo.enumtest.PizzaStatus2.自定义枚举方法现在我们大概了解了什么是枚举,如何枚举使用它们基本理解,让我们通过在枚举上定义一些额外的API方法将前面的示例提升到一个新的水平:READY){returntrue;}returnfalse;}//Methodsthatsetandgetthestatusvariable.}3.使用==比较枚举类型由于枚举类型保证了JVM中只有一个常量实例,我们可以安全地使用“==”运算符比较两个变量,如上例所示;此外,“==”运算符提供编译时和运行时安全性。首先,让我们看看以下代码片段中的运行时安全性,其中“=”运算符用于比较状态,如果两个值都为空,则不会抛出NullPointerException。相反,如果使用equals方法,将抛出NullPointerException:编译时安全再看一个例子,比较两种不同的枚举类型,用equal方法比较结果判断为真,因为getStatus方法的枚举值和另一种类型的枚举值是一致的,但是逻辑上它应该是假的。使用==运算符可以避免此问题。因为编译器会提示类型不兼容错误:if(testPz.getStatus().equals(TestColor.GREEN));if(testPz.getStatus()==TestColor.GREEN);4.在switch语句中使用枚举类型publicintgetDeliveryTimeInDays(){switch(status){caseORDERED:return5;通过在枚举类型上定义属性、方法和构造函数,它变得更加强大。接下来,让我们扩展上面的例子,从披萨的一个阶段过渡到另一个阶段,看看如何摆脱我们之前使用的if语句和switch语句:publicclassPizza{privatePizzaStatusstatus;publicenumPizzaStatus{ORDERED(5){@OverridepublicbooleanisOrdered(){returntrue;}},READY(2){@OverridepublicbooleanisReady(){returntrue;}},DELIVERED(0){@OverridepublicbooleanisDelivered(){returntrue;}};privateinttimeToDelivery;publicbooleanisOrdered(){returnfalse;}publicbooleanisReady(){return;}publicbooleanisDelivery(){returnfalse;}publicintgetTimeToDelivery(){returntimeToDelivery;}PizzaStatus(inttimeToDelivery){this.timeToDelivery=timeToDelivery;}}publicbooleanisDeliverable(){returnthis.status.isReady();}publicvervoidprintTimeToDelivery(System.ToDelivery)println("Timetodeliveryis"+this.getStatus().getTimeToDelivery());}//Methodsthatsetandgetthestatusvariable();testPz.setStatus(Pizza.PizzaStatus.READY);assertTrue(testPz.isDeliverable());}6,EnumSet和EnumMap6.1。EnumSetEnumSet是专门为枚举类型设计的Set类型,与HashSet相比,由于使用了内部位向量表示,它是一种非常高效紧凑的特定Enum常量集的表示。它为传统的基于int的“位标志”提供了一种类型安全的替代方案,使我们能够编写更易读和可维护的简洁代码。EnumSet是一个抽象类,有两个实现:RegularEnumSet和JumboEnumSet,选择哪一个取决于实例化时枚举中常量的个数。在很多场景下,枚举常量集合操作(??如:subsetting、adding、deleting、containsAll和removeAll批量操作)都非常适合使用EnumSet;如果您需要迭代所有可能的常量,请使用Enum.values()。publicclassPizza{privatestaticEnumSetundeliveredPizzaStatuses=EnumSet.of(PizzaStatus.ORDERED,PizzaStatus.READY);privatePizzaStatusstatus;publicenumPizzaStatus{...}publicbooleanisDeliverable(){returnthis.status.isReady();}publicvoidprintTimeToDeliver(){System.out.println("Timetodeliveryis"+this.getStatus().getTimeToDelivery()+"days");}publicstaticListgetAllUndeliveredPizzas(Listinput){returninput.stream().filter((s)->undeliveredPizzaStatuses。包含(s.getStatus())).collect(Collectors.toList());}publicvoiddeliver(){if(isDeliverable()){PizzaDeliverySystemConfiguration.getInstance().getDeliveryStrategy().deliver(this);this.setStatus(PizzaStatus.DELIVERED);}}//Methodsthatsetandgetthestatusvariable.}下面的测试演示了展示了EnumSet在某些场景下的强大功能:@TestpublicvoidgivenPizaOrders_whenRetrievingUnDeliveredPzs_thenCorrectlyRetrieved(){ListpzList=newArrayList<>();Pizzapz1=newPizza();pz1。pz4.setStatus(Pizza.PizzaStatus.READY);pzList.add(pz1);pzList.add(pz2);pzList.add(pz3);pzList.add(pz4);ListundeliveredPzs=Pizza.getAllUndeliveredPizzas(pzList);assertTrue(undeliveredPzs.size()==3);}6.2.EnumMapEnumMap是一个专门的映射实现,用于使用枚举常量作为键与对应的HashMap相比,它是一种高效且紧凑的实现,并且在内部表示为一个数组:EnumMapmap;让我们快速看一个演示如何在实践中使用它的真实示例:<披萨>>(PizzaStatus.class);for(Pizzaz:pizzaList){PizzaStatusstatus=pz.getStatus();if(pzByStatus.containsKey(status)){pzByStatus.get(status).add(pz);}else{ListnewnewPzList=newArrayList();newPzList.add(pz);pzByStatus.put(status,newPzList);}}returnpzByStatus;}下面的测试演示了EnumMap在某些场景下的强大功能:@TestpublicvoidgivenPizaOrders_whenGroupByStatusCalled_thenCorrectlyGrouped(){ListpzList=newArrayList>();Pizzaz1=newPizza();pz1.setStatus(Pizza.PizzaStatus.DELIVERED);Pizzapz2=newPizza();pz2.setStatus(Pizza.PizzaStatus.ORDERED);Pizzapz3=newPizza();pz3.setStatus(Pizza.PizzaStatus.ORDERED);Pizzapz4=newPizza();pz4.setStatus(Pizza.PizzaStatus.READY);pzList.add(pz1);pzList.add(pz2);pzList.add(pz3);pzList.add(pz4);EnumMap>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.通过枚举实现一些设计模式《Effective Java 》和《Java与模式》都是非常推荐的单例实现方式。以这种方式实现枚举有什么好处?《Effective Java》这个方法在功能上和公域方法类似,但是更简洁,并且免费提供序列化机制,绝对防止多次实例化,即使面对复杂的序列化或者反射攻击。虽然这个方法还没有被广泛采用,但是单元素枚举类型成为了实现Singleton的最佳方式。—-《Effective Java 中文版 第二版》”《Java与模式》“《Java与模式》”在《Java与模式》中,作者写到使用枚举实现单例实例控制会更简洁,而且免费提供了序列化机制,JVM从根本上提供了绝对防止多次实例化的保障,是一种更简洁、高效、安全的单例实现方式。”下面的代码段显示了如何使用枚举实现单例模式:publicenumPizzaDeliverySystemConfiguration{INSTANCE;PizzaDeliverySystemConfiguration(){//Initializationconfigurationwhichinvolves//overridingdefaultslikedeliverystrategy}privatePizzaDeliveryStrategydeliveryStrategy=PizzaDeliveryStrategy.NORMAL;publicstaticPizzaDeliverySystemConfigurationgetInstance(){returnINSTANCE;}publicPizzaDeliveryStrategygetDeliveryStrategy(){returndeliveryStrategy;}}如何使用它?请看下面的代码:PizzaDeliveryStrategydeliveryStrategy=PizzaDeliverySystemConfiguration.getInstance().getDeliveryStrategy();通过PizzaDeliverySystemConfiguration.getInstance()得到的是单例的PizzaDeliverySystemConfiguration7.2策略模式通常,策略模式是由不同类实现的一个接口来实现的。这也意味着增加一个新的策略意味着增加一个新的实现类。使用枚举,这个任务可以很容易地完成,添加一个新的实现意味着只需定义另一个具有特定实现的实例。以下代码片段显示了如何使用枚举实现策略模式:publicenumPizzaDeliveryStrategy{EXPRESS{@Overridepublicvoiddeliver(Pizzapz){System.out.println("Pizzawillbedeliveredinexpressmode");}},NORMAL{@Overridepublicvoiddeliver(Pizzapz){System.out.println("Pizzawillbedeliveredinnormalmode");}};publicabstractvoiddeliver(Pizzapz);}向Pizza添加以下方法:publicvoiddeliver(){if(isDeliverable()){PizzaDeliverySystemConfiguration.getInstance().getDeliveryStrategy().deliver(this);this.setStatus(PizzaStatus.DELIVERED);}}如何使用?请看下面的代码:@TestpublicvoidgivenPizaOrder_whenDelivered_thenPizzaGetsDeliveredAndStatusChanges(){Pizzaz=newPizza();pz.setStatus(Pizza.PizzaStatus.READY);pz.deliver();assertTrue(pz.getStatus()==Pizza.PizzaStatus.DELIVERED)}8、Java8和enumPizza类可以在Java8中重写,你可以看到方法lambda和StreamAPI如何让getAllUndeliveredPizzas()和groupPizzaByStatus()方法如此简洁:getAllUndeliveredPizzas():publicstaticListgetAllUndeliveredPizzas(列表<披萨>输入){返回输入。流().filter((s)->!deliveredPizzaStatus.contains(s.getStatus())).collect(Collectors.toList());}groupPizzaByStatus():publicstaticEnumMap>groupPizzaByStatus(ListpzList){EnumMap>map=pzList.stream().collect(Collectors.groupingBy(Pizza::getStatus,()->newEnumMap<>(PizzaStatus.class),Collectors.toList()));returnmap;}9.Enum类型的JSON表示使用Jackson库将枚举类型的JSON表示为POJO下面的代码片段显示了可用于相同目的的Jackson注释:@JsonFormat(shape=JsonFormat.Shape.OBJECT)publicenumPizzaStatus{ORDERED(5){@OverridepublicbooleanisOrdered(){returntrue;}},READY(2){@OverridepublicbooleanisReady(){returntrue;}},DELIVERED(0){@OverridepublicbooleanisDelivered(){returntrue;}};privateinttimeToDelivery;publicbooleanisOrdered(){returnfalse;}publicbooleanisReady(){returnfalse;}publicbooleanisDelivered(){returnfalse;}("JtimeToDelivery")publicintgetTimeToDelivery(){returntimeToDelivery;}privatePizzaStatus(inttimeToDelivery){this.timeToDelivery=timeToDelivery;}}我们可以使用Pizza和PizzaStatus如下:;System.out.println(Pizza.getJsonString(pz));生成Pizza状态以在以下JSON中显示:{"status":{"timeToDelivery":2,"ready":true,"ordered":false,"delivered":false},"deliverable":true}了解更多信息关于枚举类型的JSON序列化/反序列化(包括自定义),请参阅Jackson-将枚举序列化为JSON对象。10.小结在这篇文章中,我们讨论了Java枚举类型,从基础知识到高级应用和实际应用场景,让我们感受到了枚举的强大功能。11.补充上面我们也提到了,我们可以通过在枚举类型中定义属性、方法和构造函数来让它更强大。让我通过一个实际的例子来告诉你。我们调用短信验证码的时候,可能有几种不同的用途。我们定义如下:publicenumPinType{REGISTER(100000,"注册使用"),FORGET_PASSWORD(100001,"忘记密码使用"),UPDATE_PHONE_NUMBER(100002,"更新电话号码使用");privatefinalintcode;privatefinalStringmessage;PinType(intcode,Stringmessage){this.code=code;this.message=message;}publicintgetCode(){returncode;}publicStringgetMessage(){returnmessage;}@OverridepublicStringtoString(){return"PinType{"+"code="+code+",message='"+message+'\''+'}';}}实际使用:System.out.println(PinType.FORGET_PASSWORD.getCode());System.out.println(PinType.FORGET_PASSWORD.getMessage());System.out.println(PinType.FORGET_PASSWORD.toString());Output:100001ForgotpasswordusePinType{code=100001,message='forgotpasswordtouse'}在这种情况下,实际使用起来会非常灵活方便!