枚举类型enum并不是面试里面的一个常考点,但是为什么要给枚举写一篇博客呢,因为我简历上自己在项目中运用了枚举,11月11号在面试的时候,让我写一个枚举,我竟然没有写出来,当时就觉得很不好意思,所以这里将自己对枚举的重新认识记录一下。

一.最简单的枚举例子

enum Status{
    NORMAL(1),STOP(0),DELETE(-1);

    public Integer getStatusInt() {
        return statusInt;
    }

    private Integer statusInt;
    Status(Integer statusInt){
        this.statusInt = statusInt;
    }
}

写枚举需要注意的地方就是枚举的关键字enum,然后再是首先要申明的枚举常量,在申明的枚举常量之前不能有任何代码,常量一般用大写字母来写,枚举常量之间用逗号分割,最后用分号收尾。用这份代码祭奠我在面试中没有回答上的这道题。

二.枚举的好处

枚举在工程中随处可进,为什么要用枚举呢?枚举是将一些离散值集中起来,用同一个枚举类型来封装他们,做到一处定义,处处使用。

1.安全性
安全性体现在枚举将想要的有限的离散包含进来了,如果要是用这些值只能从枚举里面取,外面随便定义的值不被接受。
2.易于维护,可读性强

NORMAL(1),STOP(0),DELETE(-1);

给学生赋状态值

Student student = new Student();
//通过枚举赋值
student.setStatus(Status.DELETE.getStatusInt());
//通过直接赋值
student.setStatus(-1);

可以看出一个是直接通过枚举赋值,一个是直接赋值,虽然枚举赋值没有直接赋值那么简洁,但是我们一眼就可以看出用枚举赋值是什么意思,而直接赋值的话对于刚接手的人来说没有注解的话就是一头雾水。如果那天这个1被用作表示其他状态值了,还得查询整个项目是修改这个值,如果用的是枚举的话,就只需修改枚举这一块地方就好了

枚举相对于常量的好处
有很多时候枚举和常量可以相互替换,但是枚举的话相比常量会带有更多的信息,我这里说的常量类型是指基础类型和String,枚举里面可以设置多个属性,包含的信息量要多一些,有人可能会疑问我也可以将常量设置成对象,对象里面带多个类型不就好了吗?但是这样的话虽然不能改变常量的引用对象,但是确能改变引用对象的值,所以说也不是很安全。

三.枚举的基本介绍

创建枚举类,其实是在背后偷偷的帮我们继承了Enum这个抽象类的,这个抽象类是我们可以用到的
将网上其他人写的代码拿来一用一下
类聚类型定义

//定义枚举类型
enum Day {
    MONDAY, TUESDAY, WEDNESDAY,
    THURSDAY, FRIDAY, SATURDAY, SUNDAY
}

对Day进行反编译后会看到这样一个类:

//反编译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
        });
    }
}

可以看到枚举类其实是一个final类型的普通类,只是这个类继承了Enum抽象类,并且有一个私有的构造方法,在静态代码块里就实例化了这些常量。
看一下Enum这个类给提供了哪些常用方法

返回类型

方法名称

方法说明

int

compareTo(E o)

比较此枚举与指定对象的顺序

boolean

equals(Object other)

当指定对象等于此枚举常量时,返回 true。

Class<?>

getDeclaringClass()

返回与此枚举常量的枚举类型相对应的 Class 对象

String

name()

返回此枚举常量的名称,在其枚举声明中对其进行声明

int

ordinal()

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

String

toString()

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

static<T extends Enum<T>> T

static valueOf(Class<T> enumType, String name)

返回带指定名称的指定枚举类型的枚举常量

其中还有一个values,用于遍历枚举的,只是这个方法是在编译的时候才会出现在class文件里,不会出现java类中,values用户遍历定义的枚举类型所有常量。values返回的是一个数组。
其中ordinal返回的序数是根据常量摆放的顺序值返回的,不是根据你定义的时候传入值的大小摆放的,如果常量摆放的顺序发生变化,这个值也也会发生变化,定义的常量放在第二位,那么返回的就是1。

public enum EnumWeek {
    SUN("7","星期日"),
    Mon("1","星期一"),
    Tue("2","星期二"),
    WIN("3","星期三"),
    THU("4","星期四"),
    FRI("5","星期五"),
    SAT("6","星期六");


    private String no;
    private String des;
    EnumWeek(String no,String des){
         this.no=no;
         this.des=des;
    }

    @Override
    public String toString(){

        return this.no+";"+this.des;
    }

    public static void main(String[] args) {
        System.out.println(FRI.name());
        System.out.println(FRI.toString());//和valueOf输出是一样的
        System.out.println(EnumWeek.valueOf(SUN.name()));
        System.out.println(SUN.compareTo(FRI));
        System.out.println(SUN.ordinal());
        System.out.println("========遍历输出=======");
        for(EnumWeek enumWeek:EnumWeek.values()){
            System.out.println(enumWeek.toString());
        }

    }

}

输出结果:

FRI
5;星期五
7;星期日
-5
0
========遍历输出=======
7;星期日
1;星期一
2;星期二
3;星期三
4;星期四
5;星期五
6;星期六

四.枚举的基本用法

1.作为一个存储离散值的封装类,定义好后,处处使用,这也是枚举最常用的用法。
2.作为switch的参数,switch的参数不仅可以是整数,也可以是字符串,还可以是枚举类型。

enum Status{
    NORMAL(1),STOP(0),DELETE(-1);

    public Integer getStatusInt() {
        return statusInt;
    }

    private Integer statusInt;
    Status(Integer statusInt){
        this.statusInt = statusInt;
    }

    public static void switchTest(Status status){
         switch (status){
             case STOP:{
                 System.out.println("暂停");break;
             }
             case DELETE:{
                 System.out.println("删除");break;
             }
             case NORMAL:{
                 System.out.println("正常");break;
             }

         }
    }

    public static void main(String[] args) {
        switchTest(Status.DELETE);
    }

}

最后输出:删除

3.创建单例

单例模式最重要的就是一个整个堆中只有一个这样的对象,饥汉模式和双重检测都能做到在多线程的情况下创建一个对象,但是当我们使用反射的时候,是可以破坏这个单例模式的,会让单例模式失效。但是使用枚举创建单例的化就不会出现被破坏的情况
下面可以对比看一下
 

class SingleDoubleCheck{
    public static volatile SingleDoubleCheck singleDoubleCheck = null;

    private SingleDoubleCheck(){};

    public static SingleDoubleCheck getSingleDoubleCheck(){
        if(singleDoubleCheck==null){
            synchronized (SingleDoubleCheck.class){
                if(singleDoubleCheck==null){
                    singleDoubleCheck = new SingleDoubleCheck();
                }
            }
        }
        return singleDoubleCheck;
    }

}

enum EnumSingle{
    ISTANCE;

    /**
     * 如果有什么属性的化,在下面定义就好了,我这个相当于是不带任何属性的
     */

}


class Test{
    public static void main(String[] args) throws Exception{
        SingleDoubleCheck singleDoubleCheck = SingleDoubleCheck.getSingleDoubleCheck();
        Class<SingleDoubleCheck> singleDoubleCheckClass = SingleDoubleCheck.class;
        Constructor c0=  singleDoubleCheckClass.getDeclaredConstructor();
        c0.setAccessible(true);
        SingleDoubleCheck singleDoubleCheckTwo=(SingleDoubleCheck)c0.newInstance();
        System.out.println("SingleDoubleCheck对象:"+singleDoubleCheck+" ;"+singleDoubleCheckTwo.toString());
        System.out.println(singleDoubleCheck==singleDoubleCheckTwo);

        System.out.println("=================分割线===================");

        EnumSingle enumSingle = EnumSingle.ISTANCE;
        Class<EnumSingle> enumSingleClass = EnumSingle.class;
        Constructor c1 = enumSingleClass.getDeclaredConstructor();
        c1.setAccessible(true);
        EnumSingle enumSingleTwo = (EnumSingle)c1.newInstance();
        System.out.println("EnumSingle对象:"+enumSingle+" ;"+enumSingleTwo.toString());
        System.out.println(enumSingle==enumSingleTwo);
    }
}

最后输出:可以看出SingleDoubleCheck虽然是双重检测单例模式,但是我们还可以通过反射获取到实例,这就破坏了这个单例模式,而枚举的化如果想用反射来实例是会报错的

SingleDoubleCheck对象:com.example.findwork.normal.SingleDoubleCheck@7d417077 ;com.example.findwork.normal.SingleDoubleCheck@7dc36524
false
=================分割线===================
Exception in thread "main" java.lang.NoSuchMethodException: com.example.findwork.normal.EnumSingle.<init>()
	at java.lang.Class.getConstructor0(Class.java:3082)
	at java.lang.Class.getDeclaredConstructor(Class.java:2178)
	at com.example.findwork.normal.Test.main(EnumWeek.java:141)

 

五.枚举的集合类型

1.EnumMap

EnumMap存储的都是key是枚举类型的值。

class EnumMapTest{
    public static void main(String[] args) {
        Map<Status,String> enumMap = new EnumMap(Status.class);
        enumMap.put(Status.DELETE,"删除状态");
        enumMap.put(Status.STOP,"暂停状态");
        enumMap.put(Status.NORMAL,"正常状态");
        for(Map.Entry<Status,String> entry:enumMap.entrySet()){
            System.out.println("输出结果:"+entry.toString());
        }

    }
}
输出结果:NORMAL=正常状态
输出结果:STOP=暂停状态
输出结果:DELETE=删除状态

既然有HashMap,为什么要用EnumMap,这是因为EnumMap里面存储结构和HashMap不一样,效率更高效一些,EnumMap里面是两个数组,一个存储Enum类型的key值,一个存储value值,没有链表更没有红黑树,这是因为在实例化EnumMap会传入Enum class对象,这样EnumMap就知道enum有多少常量,有哪些枚举常量,这样就可以实例话key数组,存储值的时候就可以根据enum的序列位知道这个值存在数组的哪一个位置了。