Java枚举的用法和原理深入

Wesley13
• 阅读 429

转载请注明原文地址:https://www.cnblogs.com/ygj0930/p/10843644.html

一:枚举的用法

  1、定义和组织常量   

  在JDK1.5之前,我们定义常量都是:public static fianl....。有了枚举之后,我们可以把相关的常量定义到一个枚举类里,而且枚举类也提供了比常量更多的操作方法来操纵。

  用法举例:

public enum EnumTest {
    MON, TUE, WED, THU, FRI, SAT, SUN;
}

    看不懂没关系,上面的枚举类定义原理在下文中再解释。

  2、用于switch

      switch语句只支持常量值作为判断依据,枚举类型是个特例。

  用法举例:

//定义枚举
enum Signal {  
    GREEN, YELLOW, RED  
}  

//测试
public class TrafficLight {  
    Signal color = Signal.RED;  

    public void change() {  
        switch (color) {  //使用枚举来作比较
        case RED:  
            color = Signal.GREEN;  
            break;  
        case YELLOW:  
            color = Signal.RED;  
            break;  
        case GREEN:  
            color = Signal.YELLOW;  
            break;  
        }  
    }  
}  

  3、向枚举中自定义属性和方法

  我们在定义枚举时,其实是在定义一个Enum类的子类,我们可以往其中添加自定义的属性和方法。

  但是要注意:我们需要在枚举类中先定义enum实例,用分号 ; 隔开。然后才是成员变量和方法的定义。如果顺序错了会导致编译错误。【实际开发时有可能反过来,先定义成员变量和方法,再添加枚举实例】

      用法举例:

public enum Color {  
    RED("红色", 1), GREEN("绿色", 2), BLANK("白色", 3), YELLO("黄色", 4);  //定义枚举实例

    // 自定义成员变量  
    private String name;  
    private int index;  

    // 定义构造方法  
    private Color(String name, int index) {  
        this.name = name;  
        this.index = index;  
    }  
    // 添加方法  
    public static String getName(int index) {  
        for (Color c : Color.values()) {  
            if (c.getIndex() == index) {  
                return c.name;  
            }  
        }  
        return null;  
    }  
    // get set 方法  
    public String getName() {  
        return name;  
    }  
    public void setName(String name) {  
        this.name = name;  
    }  
    public int getIndex() {  
        return index;  
    }  
    public void setIndex(int index) {  
        this.index = index;  
    }  
}  

  4、重写Enum类中的方法

  定义枚举类的过程其实是定义Enum类的子类的过程,Enum类定义了一些方法,这些方法我们可以在自己定义枚举类时重写,最常见的是:重写toString()函数,返回自定义成员变量的拼接结果。

  用法举例:

public enum Color {  
    RED("红色", 1), GREEN("绿色", 2), BLANK("白色", 3), YELLO("黄色", 4);  
    // 自定义成员变量  
    private String name;  
    private int index;  
    // 构造方法  
    private Color(String name, int index) {  
        this.name = name;  
        this.index = index;  
    }  
    //重写父类方法  
    @Override  
    public String toString() {  
        return this.index+"_"+this.name;  
    }  
}  

  5、实现接口

  前面提到,用enum关键字定义枚举类时,其实是定义Enum类的子类。也就是说,我们定义的枚举类是继承自 Enum类的。

  而Java中是不支持多继承的,那如果多个枚举类型都有通用的行为时,如何进行抽象组织呢?——答案是:实现接口。

  我们可以在定义枚举类时,实现接口,重写接口中的方法来达到增加行为的目的。

//定义接口
public interface Behaviour {  
    void print();  
    String getInfo();  
}  

//定义枚举类,实现接口
public enum Color implements Behaviour{  
    RED("红色", 1), GREEN("绿色", 2), BLANK("白色", 3), YELLO("黄色", 4);  
    // 成员变量  
    private String name;  
    private int index;  
    // 构造方法  
    private Color(String name, int index) {  
        this.name = name;  
        this.index = index;  
    }  
    //接口方法  
    @Override  
    public String getInfo() {  
        return this.name;  
    }  
    //接口方法  
    @Override  
    public void print() {  
        System.out.println(this.index+":"+this.name);  
    }  
}  

  6、EnumSet的使用

  java.util.EnumSet是枚举类型的集合类,可以保证集合中的枚举元素不重复。

// EnumSet的使用
EnumSet<EnumTest> weekSet = EnumSet.allOf(aEnum.class); //将枚举类.class文件作为参数。至于为何使用.class文件,在下文中揭晓

  7、EnumMap的使用

  java.util.EnumMap中的 key是enum类型,而value则可以是任意类型。

// EnumMap的使用
 EnumMap<自定义Enum类型, String> weekMap = new EnumMap(aEnum.class);

 weekMap.put(aEnum.MON, "星期一");
 weekMap.put(aEnumt.TUE, "星期二");

二:枚举的实现——Enum类了解

   enum这个关键字,包含了:继承Enum类,定义当前类 的意思,所创建的类型都是 java.lang.Enum 类的子类。

  虽然都是定义类,但是enum关键字和class关键字的约束行为不同,class定义的类,通过new操作创建对象,想new几个就几个,而enum关键字定义的类,其实例对象,只能在这个enum类中定义好,它的实例是有限的,限制了某些东西的范围。

  如果我们不自定义枚举类的成员变量和构造方法,只定义枚举实例,则枚举实例内容都将以字符串的形式存在,在类加载的时候会通过 protected Enum(String name, int ordinal) 构造函数被创建为基本的Enum实例。 

  回到第一点中的第一个用法:

public enum EnumTest {
    MON, TUE, WED, THU, FRI, SAT, SUN;
}

  其解释过程为:

new Enum<EnumTest>("MON",0);
new Enum<EnumTest>("TUE",1);
new Enum<EnumTest>("WED",2);
    ... ...

  也就是说,这段代码实际上调用了7次 Enum(String name, int ordinal)。

  枚举类经过编译器编译之后产生的是一个class文件,该class文件经过反编译可以看到实际上是生成了一个类,该类继承了java.lang.Enum。 

  也就是说,enum 实际上就是一个 class,只不过 java 编译器帮我们做了语法的解析和编译而已,我们的枚举值也被解释成了static final修饰的常量。

public class com.hmw.test.EnumTest extends java.lang.Enum{  //我们可以看到:定义的时候的枚举值,被实例化了
    public static final com.hmw.test.EnumTest MON;
    public static final com.hmw.test.EnumTest TUE;
    public static final com.hmw.test.EnumTest WED;
    public static final com.hmw.test.EnumTest THU;
    public static final com.hmw.test.EnumTest FRI;
    public static final com.hmw.test.EnumTest SAT;
    public static final com.hmw.test.EnumTest SUN;
    static {};
    public int getValue();
    public boolean isRest();
    public static com.hmw.test.EnumTest[] values();
    public static com.hmw.test.EnumTest valueOf(java.lang.String);
    com.hmw.test.EnumTest(java.lang.String, int, int, com.hmw.test.EnumTest);
}

  Enum类中封装了一些方法,最常用的是 compareTo 和 toString。

int compareTo(E o) 
          比较此枚举与指定对象的顺序。

Class<E> getDeclaringClass() 
          返回与此枚举常量的枚举类型相对应的 Class 对象。

String name() 
          返回此枚举常量的名称,在其枚举声明中对其进行声明。

int ordinal() 
          返回枚举常量的序数(它在枚举声明中的位置,其中初始常量序数为零)。

String toString()

           返回枚举常量的名称,它包含在声明中。

static <T extends Enum<T>> T valueOf(Class<T> enumType, String name) 
          返回带指定名称的指定枚举类型的枚举常量。

三:使用枚举实现单例模式

  参考我的另一片博文;https://www.cnblogs.com/ygj0930/p/10845530.html

四:Java 枚举如何比较

  枚举实例可以使用 == 作比较,也可以 使用 equals() 作比较,二者结果一样,因为Enum类重写了 equals()方法,其实质还是使用 == 作比较的。

public final boolean equals(Object other) {
    return this==other;
}

五:switch 对枚举的支持

    来看一则 switch中用枚举作case的代码反编译结果:

package com.example.demo; import java.io.PrintStream; 
          ab public class EnumTest{ public EnumTest(){}
           public static transient void main(string args[])
           {ab a = ab.aaa;
           class _anm1 {} 
           
           switch(_cls1..SwitchMap.com.example.demo.ab[a.ordinal()]) //取的是枚举的ordinal()方法的返回值
           {
               case 1: // '\001'system.out.println("aaa");
               // fall through 
              
               case 2: // '\002'system.out.println("bbb");
               // fall through
                default:return;
            
            }

}} 

  结合上文中第四点Enum类的解读,ordinal()方法返回的是枚举类中的ordinal成员变量值(一个int值),因此switch中用枚举类型作比较时实际上是用枚举值的ordinal值作比较的。

六:枚举的序列化与反序列化

  枚举类型在序列化时仅仅是将枚举对象的name属性输出到结果中,反序列化的时候则是通过java.lang.Enum的valueOf方法来根据名字查找枚举对象

  同时,编译器禁止重写枚举类型的writeObject、readObject、readObjectNoData、writeReplace和readResolve等方法。

七:枚举的线程安全性问题——为什么说枚举类型是线程安全的

  从第二点,枚举类反编译得到的代码我们可以看到,枚举类编译出来的属性都是static类型的,而static类型的属性会在类被加载之后初始化,而Java类的加载和初始化过程都是线程安全的,所以创建一个enum类型是线程安全的。

点赞
收藏
评论区
推荐文章
blmius blmius
2年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Jacquelyn38 Jacquelyn38
2年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Stella981 Stella981
2年前
KVM调整cpu和内存
一.修改kvm虚拟机的配置1、virsheditcentos7找到“memory”和“vcpu”标签,将<namecentos7</name<uuid2220a6d1a36a4fbb8523e078b3dfe795</uuid
Wesley13 Wesley13
2年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
2年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
2年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Stella981 Stella981
2年前
Docker 部署SpringBoot项目不香吗?
  公众号改版后文章乱序推荐,希望你可以点击上方“Java进阶架构师”,点击右上角,将我们设为★“星标”!这样才不会错过每日进阶架构文章呀。  !(http://dingyue.ws.126.net/2020/0920/b00fbfc7j00qgy5xy002kd200qo00hsg00it00cj.jpg)  2
Wesley13 Wesley13
2年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
3个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这