当前位置: 首页 > 科技观察

Java中的枚举,本文完整,一些不为人知的干货

时间:2023-03-14 21:00:22 科技观察

本文转载自微信公众号《程序新视野》,作者为二哥。转载本文请联系程序新视界公众号。Java枚举,也称为Java枚举类型,是一种其字段由一组固定常量组成的类型。枚举的主要目的是强制执行编译时类型安全。enum关键字是Java中的保留关键字。在编译或设计时,当我们知道所有变量的可能性时,尽量使用枚举类型。本文将使您全面系统地了解枚举的使用以及您会遇到的一些问题。Java中的枚举枚举通常是相关常量的集合。其他编程语言早就使用枚举了,比如C++。从JDK1.5开始,Java也开始支持枚举类型。枚举是一种特殊的数据类型。它不仅是类类型,而且比类类型有一些特殊的约束。这些约束也有助于枚举类型的简单性、安全性和便利性。在Java中,枚举类型是通过enum声明的,默认继承自java.lang.Enum。因此,在声明一个枚举类时,它不能继承其他类。枚举声明在生活中,我们经常会标识方向,东、西、北、南,它们的名称、属性等基本确定了,我们可以将其声明为枚举类型:publicenumDirection{EAST,WEST,NORTH,SOUTH;}同样,一周七天也可以声明为枚举类型:enumDay{MONDAY,TUESDAY,WEDNESDAY,THURSDAY,FRIDAY,SATURDAY,SUNDAY}在没有枚举或者没有枚举的情况下,并不代表不能定义变量,我们可以通过类或接口来定义常量:publicclassDay{publicstaticfinalintMONDAY=1;publicstaticfinalintTUESDAY=2;publicstaticfinalintWEDNESDAY=3;publicstaticfinalintTHURSDAY=4;publicstaticfinalintFRIDAY=5;publicstaticfinalintSATURDAY=6;publicstaticfinalintSUNDAY=7安全易用。如果有定义相同int值的变量,混淆的几率还是很高的,编译器不会发出任何警告。所以在可以使用枚举的情况下,不提倡这种写法。枚举的底层实现上面我们已经说过,枚举是一个特殊的类,每一个枚举项本质上都是枚举类本身的一个实例。因此,上面的枚举类Direction可以用下面的代码来举例说明:然后用javap命令查看对应class文件的内容:choupangxia.枚举(java.valueenums.Directionlang.String);static{};}可以看出,一个枚举被编译器编译后,变成了一个抽象类,它继承了java.lang.Enum;而枚举中定义的枚举常量,则成为对应的publicstaticfinal属性,其类型为抽象类的类型,其名称为枚举常量的名称。枚举使用示例通过上面的反编译我们可以看出,枚举选项本质上是publicstaticfinal变量,所以直接当做这样的变量使用即可。publicclassEnumExample{publicstaticvoidmain(String[]args){Directionnorth=Direction.NORTH;System.out.println(north);//PrintsNORTH}}枚举的ordinal()方法ordinal()方法用于获取枚举变量在枚举类中声明的顺序,下标是从0开始的,这和数组中的下标很相似。它设计用于复杂的基于枚举的数据结构,例如EumSet和EnumMap。Direction.EAST.ordinal();//0Direction.NORTH.ordinal();//2需要注意的是,如果枚举项声明的位置发生变化,那么ordinal方法的值也会随之变化。所以,进来避免这种方法。否则,当枚举项很多的时候,别人在中间增加或删除了一个项,会导致后面所有的顺序变化。枚举的values()和valueOf()values()方法可以获取枚举类中的所有变量,并以数组形式返回:Direction[]directions=Direction.values();for(Directiond:directions){System.out.println(d);}//Output:EASTWESTNORTHSOUTHvalues()方法是编译器插入到枚举类中的静态方法,但在其父类Enum中不存在该方法。valueOf(Stringname)方法类似于Enum类中的valueOf方法。它根据名称获取枚举变量。也是编译器生成的,但是更加简洁,只需要传递一个参数。Directioneast=Direction.valueOf("EAST");System.out.println(east);//输出:EAST枚举命名约定按照约定,枚举是常量,所以全部使用大写字母,样式为下划线分隔(UPPER_CASE)。即枚举类名与普通类约定相同,枚举中的变量与静态变量的命名约定一致。枚举构造方法枚举类默认不需要构造方法,默认变量是声明时的字符串。当然你也可以通过自定义构造函数来初始化枚举的一些状态信息。通常我们会在构造参数中传入两个参数,比如编码和描述。以上述方向为例:publicenumDirection{//enumfieldsEAST(0),WEST(180),NORTH(90),SOUTH(270);//constructorprivateDirection(finalintangle){this.angle=angle;}//internalstateprivateintangle;publicintgetAngle(){returnangle;}}如果我们想访问每个方向的角度,可以通过一个简单的方法调用:Directionnorth=Direction.NORTH;System.out.println(north);//NORTHSystem.out.println(north.getAngle());//90System.out.println(Direction.NORTH.getAngle());//90枚举中的方法枚举是一个特殊的类,所以它也可以像普通类一样有方法和属性。枚举中不仅可以声明具体方法,还可以声明抽象方法。方法访问权限可以是私有的、受保护的和公共的。你可以通过这些方法返回枚举项的值,也可以做一些内部的私有处理。publicenumDirection{//enumfieldsEAST,WEST,NORTH,SOUTH;protectedStringprintDirection(){Stringmessage="Youaremovingin"+this+"direction";System.out.println(message);returnmessage;}}对应的方法使用如下:Direction。NORTH.printDirection();Direction.EAST.printDirection();也可以在枚举类中定义抽象方法,但必须在每个枚举项中实现相应的抽象方法:publicenumDirection{//enumfieldsEAST{@OverridepublicStringprintDirection(){Stringmessage="Youaremovingineast.Youwillfacesunineveningtime.";returnmessage;}},WEST{@OverridepublicStringprintDirection(){Stringmessage="Youaremovinginwest.Youwillfacesunineveningtime.";returnmessage;}},NORTH{@OverridepublicStringprintDirection(){Stringmessage=;Youaremovinginnorth.Youwillfaceheadindaytime.";returnmessage;}},SOUTH{@OverridepublicStringprintDirection(){Stringmessage="Youaremovinginsouth.Seaahead.";returnmessage;}};publicabstractStringprintDirection();}抽象方法d调用,与普通方法相同:Ddirection.NORTH.printDirection();Direction.EAST.printDirection();通过这种方式,您可以轻松地为每个枚举实例定义不同的行为。比如需要打印每个枚举项的方向名称,就可以定义这样一个抽象方法。上面这个枚举类的例子,似乎表现出了多态的特点,但遗憾的是,枚举类型的实例毕竟不能作为类型传递。以下方法编译器无法通过://无法编译,Direction.NORTH是一个实例对象publicvoidtext(Direction.NORTHinstance){}枚举的继承上面已经提到枚举继承自java.lang.Enum,而Enum是一个抽象类:publicabstractclassEnum>implementsComparable,Serializable{//...}也就是说,所有的枚举类都支持比较(Comparable)和序列化(Serializable)的特性。也是因为所有的枚举类都继承了Enum,所以不能继承其他类,但是可以实现接口。枚举比较所有的枚举默认都是Comparable和singleton的,所以可以用equals的方法比较,甚至可以直接用双等号“==”。Directioneast=Direction.EAST;DirectioneastNew=Direction.valueOf("EAST");System.out.println(east==eastNew);//trueSystem.out.println(east.equals(eastNew));//真枚举Collections:EnumSet和EnumMapjava.util包下引入了两个枚举集合类:EnumSet和EnumMap。EnumSetEnumSet类的定义如下:publicabstractclassEnumSet>extendsAbstractSetimplementsCloneable,java.io.Serializable{//...}EnumSet是与枚举类型一起使用的特殊Set集合。EnumSet中的所有元素都必须是枚举类型。与其他Set接口实现类HashSet/TreeSet不同,EnumSet在内部实现为一个位向量。位向量是一种极其高效的位运算。由于直接存储和操作的都是位,因此EnumSet的空间和时间性能非常可观,可以与传统的基于int的“位标志”操作相媲美。关键是我们可以像操作集集合一般用来操作位操作。EnumSet不允许使用空元素。尝试插入null将抛出NullPointerException,但测试以确定是否存在null元素或移除null元素不会抛出异常。与大多数Collection实现一样,EnumSet不是线程安全的。在多线程环境下注意数据同步问题。使用示例:publicclassTest{publicstaticvoidmain(String[]args){SetenumSet=EnumSet.of(Direction.EAST,Direction.WEST,Direction.NORTH,Direction.SOUTH);}}EnumMapEnumMap声明如下:publicclassEnumMap,V>extendsAbstractMapimplementsjava.io.Serializable,Cloneable{}类似于EnumSet,EnumMap是一个特殊的Map,Map的Key必须是枚举类型。EnumMap内部是通过数组实现的,比普通的Map效率更高。EnumMap的键值不能为null,EnumMap不是线程安全的。EnumMap使用示例如下:(方向.WEST,Direction.WEST.getAngle());enumMap.put(Direction.NORTH,Direction.NORTH.getAngle());enumMap.put(Direction.SOUTH,Direction.SOUTH.getAngle());}}enumeration用switch做条件判断时,条件参数只能是整型和字符类型,也支持枚举类型。java7之后,switch也支持字符串了。使用示例如下:enumColor{GREEN,RED,BLUE}publicclassEnumDemo4{publicstaticvoidprintName(Colorcolor){switch(color){//不用Color参考caseBLUE:System.out.println("Blue");break;caseRED:System.out.println("red");break;caseGREEN:System.out.println("green");break;}}publicstaticvoidmain(String[]args){printName(Color.BLUE);printName(Color.RED);printName(Color.GREEN);}}枚举与单例单例模式是日常使用中最常见的设计模式之一。单例的实现方式有很多种(饿汉模式、懒汉模式等),这里不做赘述,只拿最常见的单例做个对比,然后看看单例模式是如何实现的基于枚举。饿鬼模式的实现:publicclassSingleton{privatestaticSingletoninstance=newSingleton();privateSingleton(){}publicstaticSingletongetInstance(){returninstance;}}简单明了,缺点是可能会在不需要的时候创建实例,这并没有起到懒加载的效果。优点是实施简单,安全可靠。这样的单例场景,如果通过枚举实现如下:publicenumSingleton{INSTANCE;publicvoiddoSomething(){System.out.println("doSomething");}}在effectivejava中,最好的单例实现方式是枚举提升方式。利用枚举的特性,让JVM帮我们保证线程安全和单实例问题。另外,写法也很简单。可以直接通过Singleton.INSTANCE.doSomething()调用。方便、简洁、安全。总结枚举在日常编码中几乎是不可或缺的。如何用好,如何准确使用,需要有基础知识的基础。本文以此为基础,带你从头到尾看完。得到什么就点个赞吧。