目录

Enum 枚举类

基础

定义与用途

枚举类型是Java 5中新增特性的一部分,它是一种特殊的数据类型,之所以特殊是因为它既是一种类(class)类型却又比类类型多了些特殊的约束,但是这些约束的存在也造就了枚举类型的简洁性、安全性以及便捷性。

SRE实战 互联网时代守护先锋,助力企业售后服务体系运筹帷幄!一键直达领取阿里云限量特价优惠。

public abstract class Enum<E extends Enum >
extends Object
implements Comparable , Serializable

枚举可以看出是约束更严格的普通类,考虑一下如果要表现一个有序集合的内容我们可以如何定义,比如定义一周的七天,一般我们可以这么定义

//定义一周七天
public class Day {

    public static final int MONDAY =0;

    public static final int TUESDAY=1;

    public static final int WEDNESDAY=2;

    public static final int THURSDAY=3;

    public static final int FRIDAY=4;

    public static final int SATURDAY=5;

    public static final int SUNDAY=6;

}

但实际上在安全性和间接性上面都比较差。所以当面对需要定义一个有限集合时我们可以使用枚举类

基本方法

构造方法:
protected Enum(String name,int ordinal)
唯一的构造函数。 程序员无法调用此构造函数。 它由编译器响应枚举类型声明发出的代码使用。
方法摘要:

protected Object clone()
抛出CloneNotSupportedException。
int compareTo(E o)
将此枚举与指定的对象进行比较以进行订购。
boolean equals(Object other)
如果指定的对象等于此枚举常量,则返回true。
protected void finalize()
枚举类不能有finalize方法。
类 getDeclaringClass()
返回与此枚举常量的枚举类型相对应的Class对象。
int hashCode()
返回此枚举常量的哈希码。
String name()
返回此枚举常量的名称,与其枚举声明中声明的完全相同。
int ordinal()
返回此枚举常数的序数(其枚举声明中的位置,其中初始常数的序数为零)。
String toString()
返回声明中包含的此枚举常量的名称。
static <T extends Enum >T valueOf(class enumType, String name)
返回具有指定名称的指定枚举类型的枚举常量。
static <T extends Enum >T[] values()
返回枚举内容数组
Class getDeclaringClass()
返回与此枚举常量的枚举类型相对应的 Class 对象

示例

package JavaCore.Enum;

public class Enum_Demo {
    public static void main( String[] args ) {
        Day day = Day.FRIDAY;
        System.out.println(day);

        System.out.println(day.getDeclaringClass());
        
        System.out.println(Enum.valueOf(Day.class, "SUNDAY"));
        System.out.println(Day.valueOf("SUNDAY"));
        
        System.out.println(Day.FRIDAY.name());

        for (Day d:Day.values()) System.out.println(d.name());
    }
}
enum Day {
    MONDAY,TUESDAY,WEDNESDAY,THURSDAY,SATURDAY, SUNDAY,FRIDAY
}

进阶

实现原理

下面是Day反编译的源码是实现,
Day类(注意该类是final类型的,将无法被继承)而且该类继承自java.lang.Enum类,该类是一个抽象类(稍后我们会分析该类中的主要方法),除此之外,编译器还帮助我们生成了7个Day类型的实例对象分别对应枚举中定义的7个日期,这也充分说明了我们前面使用关键字enum定义的Day类型中的每种日期枚举常量也是实实在在的Day实例对象,只不过代表的内容不一样而已。注意编译器还为我们生成了两个静态方法,分别是values()和 valueOf(),稍后会分析它们的用法,到此我们也就明白了,使用关键字enum定义的枚举类型,在编译期后,也将转换成为一个实实在在的类,而在该类中,会存在每个在枚举类型中定义好变量的对应实例对象,如上述的MONDAY枚举类型对应public static final Day MONDAY;,同时编译器会为该类创建两个方法,分别是values()和valueOf()。摘自:
https://blog.csdn.net/javazejian/article/details/71333103

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

枚举与Class对象

返回类型 方法 详解
T[] getEnumConstants() 返回该枚举类型的所有元素,如果Class对象不是枚举类型,则返回null
boolean isEnum() 当且仅当该类声明为源代码中的枚举时返回 true
//正常使用
Day[] ds=Day.values();
//向上转型Enum
Enum e = Day.MONDAY;
//无法调用,没有此方法
//e.values();
//获取class对象引用
Class<?> clasz = e.getDeclaringClass();
if(clasz.isEnum()) {
    Day[] dsz = (Day[]) clasz.getEnumConstants();
    System.out.println("dsz:"+Arrays.toString(dsz));
}
/**
   输出结果:
   dsz:[MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY]
 */

自定义枚举类和构造方法及toString()

枚举类可以看成是个是一个约束更严格的普通类,他也可以有变量和方法甚至可以有main方法

package JavaCore.Enum;

import java.util.Arrays;

/**
 * 自定义枚举类的属性和构造函数
 */
public enum Enum_Advance_Custom {

    //枚举的内容必须首先写在前面,不然编译器报错,因为调用构造函数创建枚举实例只能由编译器执行,无法手动调用
    MONDAY("星期一"),
    TUESDAY("星期二"),
    WEDNESDAY("星期三"),
    THURSDAY("星期四"),
    FRIDAY("星期五"),
    SATURDAY("星期六"),
    SUNDAY("星期天");    //,逗号分隔,分号结尾

    //定义类变量desc:描述
    private String desc;

    //自定义构造方法
    Enum_Advance_Custom( String desc ) {
        this.desc = desc;
    }

    public String getDesc() {
        return desc;
    }

    //Enum的toString()可以重新但也只有toString()能够重写
    @Override
    public String toString() {
//        return super.toString();
        return desc;
    }

    public static void main( String[] args ) {
        for (Enum_Advance_Custom e : Enum_Advance_Custom.values()) {
            System.out.println(e.name() + "----" + e.getDesc());
        }

        Class c = Enum_Advance_Custom.FRIDAY.getDeclaringClass();
        for (Object enumConstant : c.getEnumConstants()) {
            System.out.println("getEnumConstants():" + enumConstant);
        }
        System.out.println("getEnumConstants():" + Arrays.toString(c.getEnumConstants()));
    }
}

Enum中使用抽象方法来实现枚举实例的多态性

枚举类中可以使用抽象方法来使每个实现抽象方法的实例产生不同的行为方式,使得枚举类具备多态的属性,但是请注意这只是多态的属性不是多态也就是只有定义在枚举类中的具体的枚举实例有不同的行为,枚举的实例是编译器实例的不是程序员,所以枚举不能像普通类一样传入不同的参数来实现多态。

package JavaCore.Enum;
/**
 * 使用抽象方法来是的实现不同抽象方法的枚举实例具备不同的行为
 */
public enum Enum_Advance_Abstract {

    FLY{
        @Override
        public String action() {
            return "I CAN FLY";
        }
    },
    WALK {
        @Override
        public String action() {
            return "I CAN WALK";
        }
    },
    SWIM {
        @Override
        public String action() {
            return "I CAN SWIM";
        }
    };

    public abstract String action();

    public static void main( String[] args ) {
        for (Enum_Advance_Abstract e: Enum_Advance_Abstract.values()) System.out.println(e.action());
    }
}

Enum与接口

Enum是个抽象类其实现类enum继承了Enum进而enum类不能再继承其他类,但enum是可以实现多接口的

package JavaCore.Enum;

/**
 * 枚举类实现接口
 * 这个示例使用了一个枚举类套实现接口的枚举类来将事物根据其运动方式来进行做了一个列表
 */
public enum Enum_Advance_Interface {

    REPTILIA(Run.reptilia.class),
    BIRD(Run.birds.class)
    ;

    private Run[] values;

    private Enum_Advance_Interface( Class<? extends Run> kind ) {

        //通过Class来获取对象枚举实例
        values = kind.getEnumConstants();

    }

    public static void main( String[] args ) {
        for (Enum_Advance_Interface e:Enum_Advance_Interface.values()) System.out.println(e);
    }

}

interface Run {
    void move();

    enum reptilia implements Run{
        MAN,
        DOG,
        CAT,
        BIG;

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

    enum birds implements Run {
        CROW,
        EAGLE,
        BAT;

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

enum和switch

在JDK1.7之后枚举类是可以直接使用在switc当中的

package JavaCore.Enum;

public class Enum_Advance_Switch {

    public static void main( String[] args ) {
        printDay(Day.MONDAY);
        printDay(Day.TUESDAY);
        printDay(Day.WEDNESDAY);
        printDay(Day.THURSDAY);
        printDay(Day.FRIDAY);
        printDay(Day.SATURDAY);
        printDay(Day.SUNDAY);
    }

    /**
     * switch语句后不加break,那么代码会一直顺序执行下去(忽略后面的case条件判断),直到break或是end语句
     * @param day
     */
    private static void printDay(Day day) {
        switch (day) {
            case MONDAY:
                System.out.println("星期一");
                break;
            case TUESDAY:
                System.out.println("星期二");
                break;
            case WEDNESDAY:
                System.out.println("星期三");
                break;
            case THURSDAY:
                System.out.println("星期四");
                break;
            case FRIDAY:
                System.out.println("星期五");
                break;
            case SATURDAY:
                System.out.println("星期六");
                break;
            case SUNDAY:
                System.out.println("星期天");
                break;
        }
    }
}

Enum与单例

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

  • 饿汉式单例:基于类加载机制避免了多线程同步问题,但无法延迟加载,当涉及资源很多时,会导致加载很慢增加初始化的负载
/**
 * 饿汉式(基于classloder机制避免了多线程的同步问题)
 */public class SingletonHungry {

    private static SingletonHungry instance = new SingletonHungry();

    private SingletonHungry() { }

    public static SingletonHungry getInstance() {
        return instance;
    }
}
  • 懒汉式单例:
    双重检验加锁,getSingleton()方法中,进行两次null检查。这样可以极大提升并发度,进而提升性能。毕竟在单例中new的情况非常少,绝大多数都是可以并行的读操作,因此在加锁前多进行一次null检查就可以减少绝大多数的加锁操作,也就提高了执行效率。但是必须注意的是volatile关键字,该关键字有两层语义:
    第一层是可见性,可见性是指在一个线程中对该变量的修改会马上由工作内存(Work Memory)写回主内存(Main Memory),所以其它线程会马上读取到已修改的值,关于工作内存和主内存可简单理解为高速缓存(直接与CPU打交道)和主存(日常所说的内存条),注意工作内存是线程独享的,主存是线程共享的。
    volatile的第二层语义是禁止指令重排序优化,我们写的代码(特别是多线程代码),由于编译器优化,在实际执行的时候可能与我们编写的顺序不同。编译器只保证程序执行结果与源代码相同,却不保证实际指令的顺序与源代码相同,这在单线程并没什么问题,然而一旦引入多线程环境,这种乱序就可能导致严重问题。
public class Singleton {
    private static volatile Singleton singleton = null;
    private Singleton(){}

    public static Singleton getSingleton(){
        if(singleton == null){
            synchronized (Singleton.class){
                if(singleton == null){
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }    
}
  • 枚举单例
package JavaCore.Enum;

/**
 * 枚举单例,枚举当中只有一个枚举实例
 * 访问:Enum.INSTANCE就能访问
 * 实例化:枚举的实例化是由编译器来操作的,其构造方法只能由编译器访问
 * 序列化:举枚序列化是由jvm保证的,每一个枚举类型和定义的枚举变量在JVM中都是唯一的,
 *         在枚举类型的序列化和反序列化上,Java做了特殊的规定:在序列化时Java仅仅是将枚举对象的name属性输出到结果中,
 *         反序列化的时候则是通过java.lang.Enum的valueOf方法来根据名字查找枚举对象。编译器是不允许任何对这种序列化机制的定制的
 *         并禁用了writeObject、readObject、readObjectNoData、writeReplace和readResolve等方法,从而保证了枚举实例的唯一性
 * 反射:反射无法创建枚举的实例,枚举的实例只能由编译器创造,在使用reflect 包中Constructor新建实例时源码中就明确判断是否为枚举类型,如果是枚举类型则无法新建实例
 *
 * 但缺点就是,枚举时占用的内存常常是静态变量的两倍还多
 */
public enum Enum_Advance_Singleton {
    INSTANCE;

    //下面如同一个正常类一样来定义实例的属性和方法
    private String name;

    public String getName() { return name; }
    public void setName( String name ) { this.name = name; }
}

枚举工具类

EnumMap

一个专门Map实现与枚举类型键一起使用。 枚举映射中的所有密钥必须来自创建映射时明确或隐式指定的单个枚举类型。 枚举地图在内部表示为数组。 这种表示非常紧凑和高效。枚举映射以其键的自然顺序 (枚举枚举常数被声明的顺序)维护。 这反映在由所述集合的视图(返回的迭代keySet() , entrySet()和values() )。由集合视图返回的迭代器弱一致 :它们不会抛出ConcurrentModificationException ,并且它们可能显示或可能不显示在迭代进行时发生的映射的任何修改的影响。不允许使用空键。 尝试插入空键将丢失NullPointerException 。 然而,尝试测试是否存在空键或删除一个将会正常工作。 允许空值。
EnumMap不同步。 如果多个线程同时访问枚举映射,并且至少有一个线程修改映射,则应该在外部进行同步。 这通常通过在自然地封装枚举映射的某些对象上进行同步来实现。 如果没有此类对象存在,则应使用Collections.synchronizedMap(java.util.Map<K, V>)方法“包装”地图。 这最好在创建时完成,以防止意外的不同步访问:
Map<EnumKey, V> m = Collections.synchronizedMap(new EnumMap<EnumKey, V>(...));

EnumSet

一个专门Set实现与枚举类型一起使用。 枚举集中的所有元素都必须来自创建集合时明确或隐式指定的单个枚举类型。 枚举集在内部表示为位向量。 这种表示非常紧凑和高效。 这个课程的空间和时间表现应该足够好,可以将其作为基于传统int的“位标志”的高品质,类型安全的替代品使用 。 即使批量操作(如containsAll和retainAll )也应该很快运行,如果他们的参数也是枚举集。
由iterator返回的迭代器以自然顺序 (枚举枚举常量的声明顺序)遍历元素。 返回的迭代器是弱一致的 :它永远不会抛出ConcurrentModificationException ,它可能显示或可能不显示在迭代进行中发生的集合的任何修改的影响。不允许使用零元素。 尝试插入一个空元素将抛出NullPointerException 。 然而,尝试测试null元素的存在或删除一个将会正常工作。
EnumSet不同步。 如果多个线程同时访问枚举集,并且至少有一个线程修改该集合,则它应该在外部同步。 这通常通过在自然地封装枚举集的一些对象上进行同步来实现。 如果没有这样的对象存在,那么该组件应该使用Collections.synchronizedSet(java.util.Set )方法“包装”。 这最好在创建时完成,以防止意外的不同步访问: Set s = Collections.synchronizedSet(EnumSet.noneOf(MyEnum.class));

本博客为Swagger-Ranger的笔记分享,文中源码地址: https://github.com/Swagger-Ranger
欢迎交流指正,如有侵权请联系作者确认删除: liufei32@outlook.com

扫码关注我们
微信号:SRE实战
拒绝背锅 运筹帷幄