Java枚举类详解_java enum code-程序员宅基地

技术标签: Java基础  

枚举类定义

//枚举类型,使用关键字enum
enum Day {
    
    MONDAY, TUESDAY, WEDNESDAY,
    THURSDAY, FRIDAY, SATURDAY, SUNDAY
}

在定义枚举类型时我们使用的关键字是enum,与class关键字类似,只不过前者是定义枚举类型,后者是定义类类型。
注意:值一般是大写的字母,多个值之间以逗号分隔。同时我们应该知道的是枚举类型可以像类(class)类型一样,定义为一个单独的文件,当然也可以定义在其他类内部,更重要的是枚举常量在类型安全性和便捷性都很有保证,如果出现类型问题编译器也会提示我们改进,但务必记住枚举表示的类型其取值是必须有限的,也就是说每个值都是可以枚举出来的。

枚举实现原理

实际上在使用关键字enum创建枚举类型并编译后,编译器会为我们生成一个相关的类,这个类继承了Java API中的java.lang.Enum类,也就是说通过关键字enum创建枚举类型在编译后事实上也是一个类类型而且该类继承自java.lang.Enum类。
查看上面枚举类Day编译后的class文件:

//反编译Day.class
final class Day extends Enum
{
    
    //编译器为我们添加的静态的values()方法
    public static Day[] values()
    {
    
        return (Day[])$VALUES.clone();
    }
    //编译器为我们添加的静态的valueOf()方法,注意间接调用了Enum也类的valueOf方法
    public static Day valueOf(String s)
    {
    
        return (Day)Enum.valueOf(com/zejian/enumdemo/Day, s);
    }
    //私有构造函数
    private Day(String s, int i)
    {
    
        super(s, i);
    }
     //前面定义的7种枚举实例
    public static final Day MONDAY;
    public static final Day TUESDAY;
    public static final Day WEDNESDAY;
    public static final Day THURSDAY;
    public static final Day FRIDAY;
    public static final Day SATURDAY;
    public static final Day SUNDAY;
    private static final Day $VALUES[];

    static 
    {
        
        //实例化枚举实例
        MONDAY = new Day("MONDAY", 0);
        TUESDAY = new Day("TUESDAY", 1);
        WEDNESDAY = new Day("WEDNESDAY", 2);
        THURSDAY = new Day("THURSDAY", 3);
        FRIDAY = new Day("FRIDAY", 4);
        SATURDAY = new Day("SATURDAY", 5);
        SUNDAY = new Day("SUNDAY", 6);
        $VALUES = (new Day[] {
    
            MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
        });
    }
}

从反编译的代码可以看出编译器确实帮助我们生成了一个Day类(注意该类是final类型的,将无法被继承)而且该类继承自java.lang.Enum类,该类是一个抽象类(稍后我们会分析该类中的主要方法),除此之外,编译器还帮助我们生成了7个Day类型的实例对象分别对应枚举中定义的7个日期,这也充分说明了我们前面使用关键字enum定义的Day类型中的每种日期枚举常量也是实实在在的Day实例对象,只不过代表的内容不一样而已。

注意编译器还为我们生成了两个静态方法,分别是values()和 valueOf(),稍后会分析它们的用法,到此我们也就明白了,使用关键字enum定义的枚举类型,在编译期后,也将转换成为一个实实在在的类,而在该类中,会存在每个在枚举类型中定义好变量的对应实例对象,如上述的MONDAY枚举类型对应public static final Day MONDAY;,同时编译器会为该类创建两个方法,分别是values()和valueOf()。

常见方法

Enum是所有 Java 语言枚举类型的公共基本类(注意Enum是抽象类),以下是它的常见方法:
在这里插入图片描述

  • ordinal()方法,该方法获取的是枚举变量在枚举类中声明的顺序,下标从0开始,如日期中的MONDAY在第一个位置,那么MONDAY的ordinal值就是0,如果MONDAY的声明位置发生变化,那么ordinal方法获取到的值也随之变化,注意在大多数情况下我们都不应该首先使用该方法,毕竟它总是变幻莫测的。
  • compareTo(E o)方法则是比较枚举的大小,注意其内部实现是根据每个枚举的ordinal值大小进行比较的。
  • name()方法与toString()几乎是等同的,都是输出变量的字符串形式。
  • 至于valueOf(Class enumType, String name)方法则是根据枚举类的Class对象和枚举名称获取枚举常量,注意该方法是静态的
public class EnumDemo {
    

    public static void main(String[] args){
    

        //创建枚举数组
        Day[] days=new Day[]{
    Day.MONDAY, Day.TUESDAY, Day.WEDNESDAY,
                Day.THURSDAY, Day.FRIDAY, Day.SATURDAY, Day.SUNDAY};

        for (int i = 0; i <days.length ; i++) {
    
            System.out.println("day["+i+"].ordinal():"+days[i].ordinal());
        }

        System.out.println("-------------------------------------");
        //通过compareTo方法比较,实际上其内部是通过ordinal()值比较的
        System.out.println("days[0].compareTo(days[1]):"+days[0].compareTo(days[1]));
        System.out.println("days[0].compareTo(days[1]):"+days[0].compareTo(days[2]));

        //获取该枚举对象的Class对象引用,当然也可以通过getClass方法
        Class<?> clazz = days[0].getDeclaringClass();
        System.out.println("clazz:"+clazz);

        System.out.println("-------------------------------------");

        //name()
        System.out.println("days[0].name():"+days[0].name());
        System.out.println("days[1].name():"+days[1].name());
        System.out.println("days[2].name():"+days[2].name());
        System.out.println("days[3].name():"+days[3].name());

        System.out.println("-------------------------------------");

        System.out.println("days[0].toString():"+days[0].toString());
        System.out.println("days[1].toString():"+days[1].toString());
        System.out.println("days[2].toString():"+days[2].toString());
        System.out.println("days[3].toString():"+days[3].toString());

        System.out.println("-------------------------------------");

        Day d=Enum.valueOf(Day.class,days[0].name());
        Day d2=Day.valueOf(Day.class,days[0].name());
        System.out.println("d:"+d);
        System.out.println("d2:"+d2);
    }
 /**
 执行结果:
   day[0].ordinal():0
   day[1].ordinal():1
   day[2].ordinal():2
   day[3].ordinal():3
   day[4].ordinal():4
   day[5].ordinal():5
   day[6].ordinal():6
   -------------------------------------
   days[0].compareTo(days[1]):-1
   days[0].compareTo(days[1]):-2
   clazz:class com.zejian.enumdemo.Day
   -------------------------------------
   days[0].name():MONDAY
   days[1].name():TUESDAY
   days[2].name():WEDNESDAY
   days[3].name():THURSDAY
   -------------------------------------
   days[0].toString():MONDAY
   days[1].toString():TUESDAY
   days[2].toString():WEDNESDAY
   days[3].toString():THURSDAY
   -------------------------------------
   d:MONDAY
   d2:MONDAY
   */

}
enum Day {
    
    MONDAY, TUESDAY, WEDNESDAY,
    THURSDAY, FRIDAY, SATURDAY, SUNDAY
}

Enum类的主要源码

//实现了Comparable
public abstract class Enum<E extends Enum<E>>
        implements Comparable<E>, Serializable {
    

    private final String name; //枚举字符串名称

    public final String name() {
    
        return name;
    }

    private final int ordinal;//枚举顺序值

    public final int ordinal() {
    
        return ordinal;
    }

    //枚举的构造方法,只能由编译器调用
    protected Enum(String name, int ordinal) {
    
        this.name = name;
        this.ordinal = ordinal;
    }

    public String toString() {
    
        return name;
    }

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

    //比较的是ordinal值
    public final int compareTo(E o) {
    
        Enum<?> other = (Enum<?>)o;
        Enum<E> self = this;
        if (self.getClass() != other.getClass() && // optimization
            self.getDeclaringClass() != other.getDeclaringClass())
            throw new ClassCastException();
        return self.ordinal - other.ordinal;//根据ordinal值比较大小
    }

    @SuppressWarnings("unchecked")
    public final Class<E> getDeclaringClass() {
    
        //获取class对象引用,getClass()是Object的方法
        Class<?> clazz = getClass();
        //获取父类Class对象引用
        Class<?> zuper = clazz.getSuperclass();
        return (zuper == Enum.class) ? (Class<E>)clazz : (Class<E>)zuper;
    }


    public static <T extends Enum<T>> T valueOf(Class<T> enumType,
                                                String name) {
    
        //enumType.enumConstantDirectory()获取到的是一个map集合,key值就是name值,value则是枚举变量值   
        //enumConstantDirectory是class对象内部的方法,根据class对象获取一个map集合的值       
        T result = enumType.enumConstantDirectory().get(name);
        if (result != null)
            return result;
        if (name == null)
            throw new NullPointerException("Name is null");
        throw new IllegalArgumentException(
            "No enum constant " + enumType.getCanonicalName() + "." + name);
    }

    //.....省略其他没用的方法
}

通过Enum源码,可以知道,Enum实现了Comparable接口,这也是可以使用compareTo比较的原因,当然Enum构造函数也是存在的,该函数只能由编译器调用,毕竟我们只能使用enum关键字定义枚举,其他事情就放心交给编译器吧。

编译器生成的Values方法与ValueOf方法

values()方法和valueOf(String name)方法是编译器生成的static方法,因此从前面的分析中,在Enum类中并没出现values()方法,但valueOf()方法还是有出现的,只不过编译器生成的valueOf()方法需传递一个name参数,而Enum自带的静态方法valueOf()则需要传递两个方法,从前面反编译后的代码可以看出,编译器生成的valueOf方法最终还是调用了Enum类的valueOf方法,下面通过代码来演示这两个方法的作用:

Day[] days2 = Day.values();
System.out.println("day2:"+Arrays.toString(days2));
Day day = Day.valueOf("MONDAY");
System.out.println("day:"+day);

/**
 输出结果:
 day2:[MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY]
 day:MONDAY
 */

从结果可知道,values()方法的作用就是获取枚举类中的所有变量,并作为数组返回,而valueOf(String name)方法与Enum类中的valueOf方法的作用类似根据名称获取枚举变量,只不过编译器生成的valueOf方法更简洁些只需传递一个参数。这里我们还必须注意到,由于values()方法是由编译器插入到枚举类中的static方法,所以如果我们将枚举实例向上转型为Enum,那么values()方法将无法被调用,因为Enum类中并没有values()方法,valueOf()方法也是同样的道理,注意是一个参数的。

 //正常使用
Day[] ds=Day.values();
//向上转型Enum
Enum e = Day.MONDAY;
//无法调用,没有此方法
//e.values();

枚举的进阶用法

在前面的分析中,我们都是基于简单枚举类型的定义,也就是在定义枚举时只定义了枚举实例类型,并没定义方法或者成员变量,实际上使用关键字enum定义的枚举类,除了不能使用继承(因为编译器会自动为我们继承Enum抽象类而Java只支持单继承,因此枚举类是无法手动实现继承的),可以把enum类当成常规类,也就是说我们可以向enum类中添加方法和变量,甚至是mian方法

向enum类添加方法与自定义构造函数

如果打算自定义自己的方法,那么必须在enum实例序列的最后添加一个分号。

重新定义一个日期枚举类,带有desc成员变量描述该日期的对于中文描述,同时定义一个getDesc方法,返回中文描述内容,自定义私有构造函数,在声明枚举实例时传入对应的中文描述,代码如下:

/**
 * Created by zejian on 2017/5/8.
 * Blog : http://blog.csdn.net/javazejian [原文地址,请尊重原创]
 */
public enum Day2 {
    
    MONDAY("星期一"),
    TUESDAY("星期二"),
    WEDNESDAY("星期三"),
    THURSDAY("星期四"),
    FRIDAY("星期五"),
    SATURDAY("星期六"),
    SUNDAY("星期日");//记住要用分号结束

    private String desc;//中文描述

    /**
     * 私有构造,防止被外部调用
     * @param desc
     */
    private Day2(String desc){
    
        this.desc=desc;
    }

    /**
     * 定义方法,返回描述,跟常规类的定义没区别
     * @return
     */
    public String getDesc(){
    
        return desc;
    }

    public static void main(String[] args){
    
        for (Day2 day:Day2.values()) {
    
            System.out.println("name:"+day.name()+
                    ",desc:"+day.getDesc());
        }
    }

    /**
     输出结果:
     name:MONDAY,desc:星期一
     name:TUESDAY,desc:星期二
     name:WEDNESDAY,desc:星期三
     name:THURSDAY,desc:星期四
     name:FRIDAY,desc:星期五
     name:SATURDAY,desc:星期六
     name:SUNDAY,desc:星期日
     */
}

从上述代码可知,在enum类中确实可以像定义常规类一样声明变量或者成员方法。但是我们必须注意到,如果打算在enum类中定义方法,务必在声明完枚举实例后使用分号分开,倘若在枚举实例前定义任何方法,编译器都将会报错,无法编译通过,同时即使自定义了构造函数且enum的定义结束,我们也永远无法手动调用构造函数创建枚举实例,毕竟这事只能由编译器执行。

也即是对于自定义成员变量,要定义对应的构造函数赋值,然后定义枚举类的时候传入相应的值,由编译器自动执行构造函数进行赋值,而不能手动进行赋值。

同时要想获取自定义成员变量值,

enum类中定义抽象方法

与常规抽象类一样,enum类允许我们为其定义抽象方法,然后使每个枚举实例都实现该方法,以便产生不同的行为方式,注意abstract关键字对于枚举类来说并不是必须的如下:

public enum EnumDemo3 {
    

    FIRST{
    
        @Override
        public String getInfo() {
    
            return "FIRST TIME";
        }
    },
    SECOND{
    
        @Override
        public String getInfo() {
    
            return "SECOND TIME";
        }
    }
    ;

    /**
     * 定义抽象方法
     * @return
     */
    public abstract String getInfo();

    //测试
    public static void main(String[] args){
    
        System.out.println("F:"+EnumDemo3.FIRST.getInfo());
        System.out.println("S:"+EnumDemo3.SECOND.getInfo());
        /**
         输出结果:
         F:FIRST TIME
         S:SECOND TIME
         */
    }
}

通过这种方式就可以轻而易举地定义每个枚举实例的不同行为方式。我们可能注意到,enum类的实例似乎表现出了多态的特性,可惜的是枚举类型的实例终究不能作为类型传递使用,就像下面的使用方式,编译器是不可能答应的:

//无法通过编译,毕竟EnumDemo3.FIRST是个实例对象 public void text(EnumDemo3.FIRST instance){ }

enum类与接口

由于Java单继承的原因,enum类并不能再继承其它类,但并不妨碍它实现接口,因此enum类同样是可以实现多接口的,如下:

interface food{
    
    void eat();
}

interface sport{
    
    void run();
}

public enum EnumDemo2 implements food ,sport{
    
    FOOD,
    SPORT,
    ; //分号分隔

    @Override
    public void eat() {
    
        System.out.println("eat.....");
    }

    @Override
    public void run() {
    
        System.out.println("run.....");
    }
}

枚举与单例模式

Java单例模式实现方式:
http://www.blogjava.net/kenzhh/archive/2013/03/15/357824.html

单例模式可以说是最常使用的设计模式了,它的作用是确保某个类只有一个实例,自行实例化并向整个系统提供这个实例。在实际应用中,线程池、缓存、日志对象、对话框对象常被设计成单例,总之,选择单例模式就是为了避免不一致状态,下面我们将会简单说明单例模式的几种主要编写方式,从而对比出使用枚举实现单例模式的优点。首先看看饿汉式的单例模式:

public class SingletonHungry {
    

    private static SingletonHungry instance = new SingletonHungry();

    private SingletonHungry() {
    
    }

    public static SingletonHungry getInstance() {
    
        return instance;
    }
}

枚举类实现单例模式:

实现方式一:枚举类本身就是单例对象,内部可实现单例对象具体方法逻辑

Java虚拟机会保证枚举对象的唯一性,因此每一个枚举类型和定义的枚举变量在JVM****中都是唯一的。

public enum Singleton {
    
     INSTANCE;
     public void businessMethod() {
    
          System.out.println("我是一个单例!");
     }
}

大家可以看到,我们定义了一个枚举类型Singleton,在其中定义了一个枚举变量instance,同时还提供了业务方法businessMethod。

实现方式二:枚举类作为单例对象内部类

​ 如果需要将一个已有的类改造为单例类,也可以使用枚举的方式来实现。

public class Singleton {
    
    private Singleton(){
    
    }   
    public static enum SingletonEnum {
    
        SINGLETON;
        private Singleton instance = null;
        private SingletonEnum(){
    
            instance = new Singleton();
        }
        public Singleton getInstance(){
    
            return instance;
        }
    }
}

在代码中,我们首先将Singleton类的构造函数设置为private私有的,然后在Singleton类中定义一个静态的枚举类型SingletonEnum。

​ 在SingletonEnum中定义了枚举类型的实例对象Singleton,再按照单例模式的要求在其中定义一个Singleton类型的对象instance,其初始值为null;我们需要将SingletonEnum的构造函数改为私有的,在私有构造函数中创建一个Singleton的实例对象;最后在getInstance()方法中返回该对象。

​ 在实现过程中,Java虚拟机会保证枚举类型不能被反射并且构造函数只被执行一次。

​ 正如我们前面所讲的Singleton类本身就是一个普通类,它里面还包含了其他业务方法。在这里我们只需要在其中增加一个内部枚举类型来存储和创建它的唯一实例即可,这和前面的静态内部类的实现有点相似,但是枚举实现可以很好地解决反射和反序列化会破坏单例模式的问题,提供了一种更加安全和可靠的单例模式实现机制。

参考文献

https://blog.csdn.net/javazejian/article/details/71333103

https://blog.csdn.net/qq_27093465/article/details/52180865

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/sinat_41847122/article/details/118266317

智能推荐

halcon异常情况汇总_用halcon盗版的后果-程序员宅基地

文章浏览阅读4.3k次。一、VS下不易发现的内存泄露 在VS下调用Halcon算子时,要特别注意:所有输出的变量,不管有没有用到,都不要用NULL来替代。否则会导致程序会有2-4kb的内存泄露。(Halcon12及以前版本均存在这个问题,最新的版本是否存在有待验证)  例如,我们需要计算某个区域的面积,而不关心另两个一并计算得出的中心点坐标,可能会这样写: 1 2 ..._用halcon盗版的后果

【Metahuman&Maya】Metahuman导入Maya_could not send data over port 13291-程序员宅基地

文章浏览阅读7.2k次,点赞4次,收藏17次。Metahuman导入maya_could not send data over port 13291

刷题记录之 webbuuctf 2_can you find out the flag?-程序员宅基地

文章浏览阅读469次。刷题记录之 webbuuctf 2目录刷题记录之 webbuuctf 2IncludeSecret FileInclude看到题目之后看到是Include想到可能是文件包含或者是伪协议利用的题目,打开题目之后是个很简洁的页面,只有一个链接打开这个链接之后是一句话:Can you find out the flag?问你是否能够找到flag,没有其他有用的信息,然后我就查看网页源代码:发现这里面有个一变量file和flag.php但是访问flag.php返回的就是Can you find ou_can you find out the flag?

用docker打包vim,构建随取随用的开发环境_docker 打包vim-程序员宅基地

文章浏览阅读1.2k次,点赞3次,收藏2次。docker的核心理念就是:Build once,run anywhere。对于Vimer来说,这无疑是一个好消息。大家都知道,虽说有vimrc在手,但有些插件的安装并不是那么容易,需要进行各种预处理和后处理;某些插件在不同发行版上的表现也不一致。一旦切换到新环境,一时半会儿肯定上不了手,如果缺乏网络支持,那更是捉襟见肘。基于以上几点考虑,设想可以通过docker打包vim,构建一个镜像,将其作..._docker 打包vim

修改表字段相关的sql_修改表字段名称sql-程序员宅基地

文章浏览阅读975次。修改表字段相关的sql_修改表字段名称sql

高级安卓开发面试题,史上最全的Android开发索引帖,Android开发两年-程序员宅基地

文章浏览阅读939次,点赞25次,收藏27次。由于文章篇幅原因,我只把面试题列了出来,详细的答案,我整理成了一份PDF文档,这份文档还包括了还有高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料 ,帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习。本文已被CODING开源项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》收录一个人可以走的很快,但一群人才能走的更远。如果你从事以下工作或对以下感兴趣,欢迎戳这里加入程序员的圈子,让我们一起学习成长!

随便推点

2023年6月墨天轮中国图数据库排行榜:TGS 开新局,创邻和字节多点突破露锋芒_图数据库排名-程序员宅基地

文章浏览阅读2.5k次。墨天轮社区的中国数据库中排行榜已有31个图数据库参与排名,那么这些图数据库排名如何?哪些又将成为黑马?欢迎查看新出炉6月图数据库榜单解读文章一起讨论。_图数据库排名

JACO2、MICO2开机绿灯一直闪烁无法正常使用方法问题_mico2 型号机械臂-程序员宅基地

文章浏览阅读1.2k次。问题描述:许多童鞋在使用轻量型机械臂JACO2、MICO2,突然一天开机手柄的绿灯一直闪烁,无法初始化完成。以至于手柄不能操作。连上电脑SDK也无法识别序列号,也就是正常的操作,啥也干不了。 出现这种情况,不用着急,这种问题只是软件上的小问题,是可以完美修复的,当然需要修复好,需要你联系你购买手臂的代理商,他们会帮你修复的,一般处理这种情况,我选择最直接了当的方式,就是刷机。。。刷机。。。^ ^..._mico2 型号机械臂

使用NVM安装NodeJS并解决npm下载依赖失效问题(最全流程)_now using node v16.15.0 (64-bit)-程序员宅基地

文章浏览阅读7k次,点赞10次,收藏23次。本人因为刚学习react,需要创建项目。下载过react依赖后才发现黑窗口提示我因node版本过低无法创建项目,怎么办呢?我去网上看了很多node升级的文章,大多数说的是重新去官网下载更高版本,我就跟着他们一步步走,但是很快问题就出现了。先是文章中说应该更改node缓存地址,防止给C盘造成过大压力。这就涉及到更改环境变量,我一顿操作后忽然发现我新安装的node版本查不到。emmmmmm 这是为什么呢?我去看了我的文件夹,发现之前的NodeJs版本是由nvm管理的。哦~~~原来是这样。我立马改道又去搜了nvm_now using node v16.15.0 (64-bit)

详解:hive启动hiveserver2连JDBC报错:Could not open client transport with JDBC Uri 解决方案_could not open client transport with jdbc uri: jdb-程序员宅基地

文章浏览阅读4.4k次。hive启动hiveserver2连JDBC报错:Could not open client transport with JDBC Uri 解决方案 [hadoop@hadoop001 bin]$ ./beeline -u jdbc:hive2://hadoop001:10000/default -n hadoopls: cannot access /home/hadoop/app/spar..._could not open client transport with jdbc uri: jdbc:hive2://localhost:10000/

Sublime Text 3中文乱码问题的解决(最有效)_sublime text 显示乱码-程序员宅基地

文章浏览阅读2.2w次,点赞5次,收藏13次。 Sublime Text 3中文乱码问题的解决(最有效) Sublime Text 3是很好的代码编辑器,没有之一,因为她的性感高亮代码配色,更因为它的小巧,但是它默认不支持GBK的编码格式,因此打开GBK的代码文件,如果里面有中文的话,就会乱码,如下所示: 解决步骤如下: 大家如果是在官网下载的Sublime Text 3,那么首先需要安装一个 Pack..._sublime text 显示乱码

机器学习概述-程序员宅基地

文章浏览阅读5.2k次,点赞6次,收藏80次。机器学习是一种人工智能技术,通过对数据的学习和分析,让计算机系统自动提高其性能。简而言之,机器学习是一种从数据中学习规律和模式的方法,通过数据来预测、分类或者决策。_机器学习