当前位置:主页 > 查看内容

Java中的枚举,这一篇全了,一些不为人知的干货

发布时间:2021-04-28 00:00| 位朋友查看

简介:本文转载自微信公众号「程序新视界」,作者二师兄。转载本文请联系程序新视界公众号。 Java枚举,也称作Java枚举类型,是一种字段由一组固定常量集合组成的类型。枚举的主要目的是加强编译时类型的安全性。enum关键字是Java中的保留关键字。 在编译或设计时……

本文转载自微信公众号「程序新视界」,作者二师兄。转载本文请联系程序新视界公众号

Java枚举,也称作Java枚举类型,是一种字段由一组固定常量集合组成的类型。枚举的主要目的是加强编译时类型的安全性。enum关键字是Java中的保留关键字。

在编译或设计时,当我们知道所有变量的可能性时,尽量使用枚举类型。本篇文章就带大家全面系统的了解枚举的使用,以及会遇到的一些问题。

Java中的枚举

枚举通常是一组相关的常量集合,其他编程语言很早就开始用枚举了,比如C++。从JDK1.5起,Java也开始支持枚举类型。

枚举是一种特殊的数据类型,它既是一种类(class)类型却又比类类型多了些特殊的约束,这些约束也造就了枚举类型的简洁性、安全性以及便捷性。

在Java中,通过enum来声明枚举类型,默认继承自java.lang.Enum。所以声明枚举类时无法再继承其他类。

枚举声明

在生活中我们会经常辨认方向,东南西北,它们的名称、属性等基本都是确定的,我们就可以将其声明为枚举类型:

  1. public enum Direction { 
  2.    EAST, WEST, NORTH, SOUTH; 

同样,每周七天也可以声明成枚举类型:

  1. enum Day { 
  2.     MONDAY, TUESDAY, WEDNESDAY, 
  3.     THURSDAY, FRIDAY, SATURDAY, SUNDAY 

在没有枚举或没使用枚举的情况下,并不是说不可以定义变量,我们可以通过类或接口进行常量的定义:

  1. public class Day { 
  2.  
  3.     public static final int MONDAY =1; 
  4.  
  5.     public static final int TUESDAY=2; 
  6.  
  7.     public static final int WEDNESDAY=3; 
  8.  
  9.     public static final int THURSDAY=4; 
  10.  
  11.     public static final int FRIDAY=5; 
  12.  
  13.     public static final int SATURDAY=6; 
  14.  
  15.     public static final int SUNDAY=7; 
  16.  

但这样存在许多不足,如在类型安全和使用方便性上。如果存在定义int值相同的变量,混淆的几率还是很大的,编译器也不会提出任何警告。因此,当能使用枚举的时候,并不提倡这种写法。

枚举的底层实现

上面我们已经说了,枚举是一个特殊的类,每一个枚举项本质上都是枚举类自身的实例。

因此,上面枚举类Direction可以通过下面代码进行示例:

  1. final class Direction extends Enum{ 
  2.     public final static Direction EAST = new Direction(); 
  3.     public final static Direction WEST = new Direction(); 
  4.     public final static Direction NORTH = new Direction(); 
  5.     public final static Direction SOUTH = new Direction(); 

首先通过javac命令对Direction进行编译,然后通过javap命令来查看一下对应class文件内容:

  1. bogon:enums apple$ javap Direction.class  
  2. Compiled from "Direction.java" 
  3. public final class com.choupangxia.enums.Direction extends java.lang.Enum<com.choupangxia.enums.Direction> { 
  4.   public static final com.choupangxia.enums.Direction EAST; 
  5.   public static final com.choupangxia.enums.Direction WEST; 
  6.   public static final com.choupangxia.enums.Direction NORTH; 
  7.   public static final com.choupangxia.enums.Direction SOUTH; 
  8.   public static com.choupangxia.enums.Direction[] values(); 
  9.   public static com.choupangxia.enums.Direction valueOf(java.lang.String); 
  10.   static {}; 

可以看到,一个枚举在经过编译器编译过后,变成了一个抽象类,它继承了java.lang.Enum;而枚举中定义的枚举常量,变成了相应的public static final属性,而且其类型就抽象类的类型,名字就是枚举常量的名字。

枚举使用实例

通过上面的反编译我们可以看到,枚举的选项本质上就是public static final的变量,所以就把它当做这样的变量使用即可。

  1. public class EnumExample { 
  2.     public static void main(String[] args) { 
  3.         Direction north = Direction.NORTH; 
  4.         System.out.println(north);        //Prints NORTH 
  5.     } 

枚举的ordinal()方法

ordinal()方法用于获取枚举变量在枚举类中声明的顺序,下标从0开始,与数组中的下标很相似。它的设计是用于EumSet和EnumMap复杂的基于枚举的数据结构使用。

  1. Direction.EAST.ordinal();     //0 
  2.   
  3. Direction.NORTH.ordinal();    //2 

需要注意的是如果枚举项声明的位置发生了变化,那么ordinal方法的值也随之变化。所以,进来避免使用该方法。不然,当枚举项比较多时,别人在中间增删一项,会导致后续的所有顺序变化。

枚举的values()和valueOf()

values()方法可获取枚举类中的所有变量,并作为数组返回:

  1. Direction[] directions = Direction.values(); 
  2.   
  3. for (Direction d : directions) { 
  4.     System.out.println(d); 
  5.   
  6. //Output
  7.   
  8. EAST 
  9. WEST 
  10. NORTH 
  11. SOUTH 

values()方法是由编译器插入到枚举类中的static方法,而它的父类Enum中并不存在这个方法。

valueOf(String name)方法与Enum类中的valueOf方法的作用类似根据名称获取枚举变量,同样是由编译器生成的,但更简洁些,只需传递一个参数。

  1. Direction east = Direction.valueOf("EAST"); 
  2.           
  3. System.out.println(east); 
  4.   
  5. //Output
  6.   
  7. EAST 

枚举命名约定

按照约定,枚举属于常量,因此采用所有字母大写,下划线分割的风格(UPPER_CASE)。也就是说枚举类名与普通类约定一样,而枚举中的变量与静态变量的命名规范一致。

枚举的构造方法

默认情况下,枚举类是不需要构造方法的,默认的变量就是声明时的字符串。当然,你也可以通过自定义构造方法,来初始化枚举的一些状态信息。通常情况下,我们会在构造参数中传入两个参数,比如,一个编码,一个描述。

以上面的方向为例:

  1. public enum Direction { 
  2.     // enum fields 
  3.     EAST(0), WEST(180), NORTH(90), SOUTH(270); 
  4.   
  5.     // constructor 
  6.     private Direction(final int angle) { 
  7.         this.angle = angle; 
  8.     } 
  9.   
  10.     // internal state 
  11.     private int angle; 
  12.   
  13.     public int getAngle() { 
  14.         return angle; 
  15.     } 

如果我们想访问每个方向的角度,可以通过简单的方法调用:

  1. Direction north = Direction.NORTH; 
  2.           
  3. System.out.println(north);                      //NORTH 
  4.   
  5. System.out.println(north.getAngle());           //90 
  6.   
  7. System.out.println(Direction.NORTH.getAngle()); //90 

枚举中的方法

枚举就是一个特殊的类,因此也可以像普通的类一样拥有方法和属性。在枚举中不仅可以声明具体的方法,还可以声明抽象方法。

方法的访问权限可以是private、protected和public。可以通过这些方法返回枚举项的值,也可以做一些内部的私有处理。

  1. public enum Direction { 
  2.     // enum fields 
  3.     EAST, WEST, NORTH, SOUTH; 
  4.       
  5.     protected String printDirection() { 
  6.         String message = &quot;You are moving in &quot; + this + &quot; direction&quot;; 
  7.         System.out.println( message ); 
  8.         return message; 
  9.     } 

对应方法的使用如下:

  1. Direction.NORTH.printDirection();  
  2. Direction.EAST.printDirection();  

枚举类中还可以定义抽象的方法,但每个枚举项中必须实现对应的抽象方法:

  1. public enum Direction  
  2.     // enum fields 
  3.     EAST { 
  4.         @Override 
  5.         public String printDirection() { 
  6.             String message = &quot;You are moving in east. You will face sun in morning time.&quot;; 
  7.             return message; 
  8.         } 
  9.     }, 
  10.     WEST { 
  11.         @Override 
  12.         public String printDirection() { 
  13.             String message = &quot;You are moving in west. You will face sun in evening time.&quot;; 
  14.             return message; 
  15.         } 
  16.     }, 
  17.     NORTH { 
  18.         @Override 
  19.         public String printDirection() { 
  20.             String message = &quot;You are moving in north. You will face head in daytime.&quot;; 
  21.             return message; 
  22.         } 
  23.     }, 
  24.     SOUTH { 
  25.         @Override 
  26.         public String printDirection() { 
  27.             String message = &quot;You are moving in south. Sea ahead.&quot;; 
  28.             return message; 
  29.         } 
  30.     }; 
  31.   
  32.     public abstract String printDirection(); 

抽象方法的调用,与普通方法一样:

  1. Direction.NORTH.printDirection();  
  2. Direction.EAST.printDirection();  

通过这种方式就可以轻而易举地定义每个枚举实例的不同行为方式。比如需要每个枚举项都打印出方向的名称,就可以定义这么一个抽象的方法。

上面的实例enum类似乎表现出了多态的特性,可惜的是枚举类型的实例终究不能作为类型传递使用。下面的方式编译器都无法通过:

  1. //无法通过编译,Direction.NORTH是个实例对象 
  2.  public void text(Direction.NORTH instance){ } 

枚举的继承

上面已经提到过枚举继承自java.lang.Enum,Enum是一个抽象类:

  1. public abstract class Enum<E extends Enum<E>> 
  2.         implements Comparable<E>, Serializable { 
  3.     // ... 

也就是说,所有的枚举类都支持比较(Comparable)和序列化(Serializable)的特性。也正因为所有的枚举类都继承了Enum,所以无法再继承其他类了,但是可以实现接口。

枚举的比较

所有的枚举默认都是Comparable和单例的,因此可以通过equals方法进行比较,甚至可以直接用双等号“==”进行比较。

  1. Direction east = Direction.EAST; 
  2. Direction eastNew = Direction.valueOf("EAST"); 
  3.   
  4. System.out.println( east == eastNew );           //true 
  5. System.out.println( east.equals( eastNew ) );    //true 

枚举集合:EnumSet和EnumMap

在java.util包下引入了两个枚举集合类:EnumSet和EnumMap。

EnumSet

EnumSet类的定义如下:

  1. public abstract class EnumSet<E extends Enum<E>> extends AbstractSet<E> 
  2.     implements Cloneable, java.io.Serializable
  3.     // ... 

EnumSet是与枚举类型一起使用的专用Set集合,EnumSet中所有元素都必须是枚举类型。与其他Set接口的实现类HashSet/TreeSet不同的是,EnumSet在内部实现是位向量。

位向量是一种极为高效的位运算操作,由于直接存储和操作都是bit,因此EnumSet空间和时间性能都十分可观,足以媲美传统上基于int的“位标志”的运算,关键是我们可像操作set集合一般来操作位运算。

EnumSet不允许使用null元素,试图插入null将抛出 NullPointerException,但测试判断是否存在null元素或移除null元素则不会抛出异常,与大多数Collection实现一样,EnumSet不是线程安全的,在多线程环境下需注意数据同步问题。

使用实例:

  1. public class Test { 
  2.    public static void main(String[] args) { 
  3.      Set enumSet = EnumSet.of(  Direction.EAST, 
  4.                                 Direction.WEST, 
  5.                                 Direction.NORTH, 
  6.                                 Direction.SOUTH 
  7.                               ); 
  8.    } 
  9.  } 

EnumMap

EnumMap的声明如下:

  1. public class EnumMap<K extends Enum<K>, V> extends AbstractMap<K, V> 
  2.     implements java.io.Serializable, Cloneable 
  3. {} 

与EnumSet类似,EnumMap是一个特殊的Map,Map的Key必须是枚举类型。EnumMap内部是通过数组实现的,效率比普通的Map更高一些。EnumMap的key值不能为null,并且EnumMap也不是线程安全的。

EnumMap使用实例如下:

  1. public class Test { 
  2.   public static void main(String[] args){ 
  3.     //Keys can be only of type Direction 
  4.     Map enumMap = new EnumMap(Direction.class); 
  5.   
  6.     //Populate the Map 
  7.     enumMap.put(Direction.EAST, Direction.EAST.getAngle()); 
  8.     enumMap.put(Direction.WEST, Direction.WEST.getAngle()); 
  9.     enumMap.put(Direction.NORTH, Direction.NORTH.getAngle()); 
  10.     enumMap.put(Direction.SOUTH, Direction.SOUTH.getAngle()); 
  11.   } 

枚举与switch

使用switch进行条件判断时,条件参数一般只能是整型,字符型,同时也支持枚举型,在java7后switch也对字符串进行了支持。

使用实例如下:

  1. enum Color {GREEN,RED,BLUE} 
  2.  
  3. public class EnumDemo4 { 
  4.  
  5.     public static void printName(Color color){ 
  6.         switch (color){ 
  7.             //无需使用Color进行引用 
  8.             case BLUE:  
  9.                 System.out.println("蓝色"); 
  10.                 break; 
  11.             case RED: 
  12.                 System.out.println("红色"); 
  13.                 break; 
  14.             case GREEN: 
  15.                 System.out.println("绿色"); 
  16.                 break; 
  17.         } 
  18.     } 
  19.  
  20.     public static void main(String[] args){ 
  21.         printName(Color.BLUE); 
  22.         printName(Color.RED); 
  23.         printName(Color.GREEN); 
  24.     } 

枚举与单例

单例模式是日常使用中最常见的设计模式之一了,单例的实现有很多种实现方法(饿汉模式、懒汉模式等),这里就不再赘述,只以一个最普通的单例来做对照,进而看看基于枚举如何来实现单例模式。

饿汉模式的实现:

  1. public class Singleton { 
  2.  
  3.     private static Singleton instance = new Singleton(); 
  4.  
  5.     private Singleton() { 
  6.     } 
  7.  
  8.     public static Singleton getInstance() { 
  9.         return instance; 
  10.     } 

简单直接,缺点是可能在还不需要时就把实例创建出来了,没起到lazy loading的效果。优点就是实现简单,而且安全可靠。

这样一个单例场景,如果通过枚举进行实现如下:

  1. public enum Singleton { 
  2.  
  3.     INSTANCE; 
  4.  
  5.     public void doSomething() { 
  6.         System.out.println("doSomething"); 
  7.     } 

在effective java中说道,最佳的单例实现模式就是枚举模式。利用枚举的特性,让JVM来帮我们保证线程安全和单一实例的问题。除此之外,写法还特别简单。

直接通过Singleton.INSTANCE.doSomething()的方式调用即可。方便、简洁又安全。

小结

枚举在日常编码中几乎是必不可少的,如何用好,如何用精,还需要基础知识的铺垫,本文也正是基于此带大家从头到尾梳理了一遍。有所收获就点个赞吧。


本文转载自网络,原文链接:https://mp.weixin.qq.com/s/mAhiQcBOCKT9MnT6sNS3BQ
本站部分内容转载于网络,版权归原作者所有,转载之目的在于传播更多优秀技术内容,如有侵权请联系QQ/微信:153890879删除,谢谢!
上一篇:利用 BATS 测试 Bash 脚本和库 下一篇:没有了

推荐图文

  • 周排行
  • 月排行
  • 总排行

随机推荐