你真的会用枚举Enum吗?

枚举介绍

枚举是Java中有着各种特殊约束的类的集合,但是这个类被严重的约束着,但也正是这些特殊的约束才使得枚举成为枚举。

Enumjava.lang包中的一个抽象类,实现了Comparable,以及Serializable接口,是jdk1.5提供的新特性,主要是方便习惯用枚举操作的人员,所以在java中枚举事实上处于一种可有可无的地位,毕竟在jdk1.5以前,是不提供枚举的,依然不影响开发,但是通过使用枚举可以提高程序的可读性以及健壮性。另外通过enum关键字修饰的类默认继承自Enum类,若是自定义了构造方法,则所有枚举实例必须调用自定义的构造方法初始化。

枚举的释义是“一一列举”,字面上我们就可以得到枚举的广义理解,是一个可被列举的集合,因为它是可被列举的,使得它必须有一个可穷尽的范围。以及不可变性,不能每次列举出来的内容都是不一样的,集合中的内容应该是最初就定义好的,不可修改的。

枚举的意义

枚举用于声明一组命名的常数,当一个变量有几种可能的取值时,可以将它定义为枚举类型,其意义如下:

  • 代码可读性:通过使用枚举,我们可以使得代码更清晰,因为它可以描述特定的值,并且有效地防止用户提供无效值。

  • 代码安全:规范了参数的形式、调用时类型取值范围确定,不用考虑类型的不匹配、显式的替代了int型参数可能带来的模糊概念、减少程序中因为类型引发的问题

  • 耦合性低、扩展性高:便于增加类别,与原代码的耦合性低;相比较于定义常量,扩展性更高

  • 枚举是线程安全的:我们定义的一个枚举,在第一次被真正用到的时候,会被虚拟机加载并初始化,而这个初始化过程是线程安全的。而我们知道,解决单例的并发问题,主要解决的就是初始化过程中的线程安全问题。所以,枚举实现的单例是天生线程安全的。

  • 反序列化枚举类型不会创建新的实例:枚举类型在序列化的时候Java仅仅是将枚举对象的name属性输出到结果中,反序列化的时候则是通过java.lang.Enum的valueOf方法来根据名字查找枚举对象。同时,编译器是不允许任何对这种序列化机制的定制的,因此禁用了writeObject、readObject、readObjectNoData、writeReplace和readResolve等方法。普通类的反序列化是通过反射实现的,枚举类的反序列化不是通过反射实现的。所以,枚举类也不会产生由于反序列化导致单例破坏的问题。

枚举的用法

  • 首先看一下不使用枚举的例子:
public class ColorFinal {
    public String name;
    public static final ColorFinal RED = new ColorFinal("红色");
    public static final ColorFinal GREED = new ColorFinal("绿色");
    public static final ColorFinal BLUE = new ColorFinal("蓝色");

    public ColorFinal(String name){
        this.name = name;
    }
}
  • 而使用枚举的例子如下:
public enum ColorEnum {
    RED,GREEN,BLUE
}

使用构造方法赋值

  • 若是要给每个枚举实例赋值则需要定义构造方法(若是定义构造方法,则所有实例必须明确使用此构造方法初始化):
public enum ColorEnum1 {
    RED("红色"),GREEN("绿色"),BLUE("蓝色");

    String name;

    ColorEnum1(String name){
        this.name = name;
    }

    public String getName() {
        return name;
    }

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

枚举值的遍历

  • 同时也方便了我们轻松获取枚举的取值范围:
public class EnumTest {

    @Test
    public void testForEach(){
        for(ColorEnum1 c : ColorEnum1.values()){
            System.out.println(c.name()+"-->"+c.ordinal());
        }
    }
}

这里写图片描述

使用 == 比较枚举类型

由于枚举类型确保JVM中仅存在一个常量实例,因此我们可以安全地使用 == 运算符比较两个变量,如上例所示;此外,== 运算符可提供编译时和运行时的安全性,不同枚举类的实例比较会在编译阶段报错。
在这里插入图片描述

我们也可以使用equals比较两个不同枚举类实例的值:

@Test
public void testEquals(){
    System.out.println(ColorEnum1.RED.equals(ColorEnum2.RED));//false
}

在 switch 语句中使用枚举类型

  • 于此同时,在jdk1.5后switch中还加入了对Enum对象的判断功能:
    @Test
    public void testSwitch(){
        switch (ColorEnum.RED){
            case RED:
                System.out.println("这是红色");
                break;
            case GREEN:
                System.out.println("这是绿色");
                break;
            case BLUE:
                System.out.println("这是蓝色");
                break;

        }
    }

这里写图片描述

Enum类通过实现接口扩展功能

  • 同时我们可以让枚举实现各种接口:
interface ColorInfo {
    public void printColor();
}


public enum ColorEnum2 implements ColorInfo {
    RED("红色") {
        public void printColor() {
            System.out.println("最喜欢的颜色是"+name);
        }
    },
    GREEN("绿色") {
        public void printColor() {
            System.out.println("最富有活力的颜色:"+name);
        }
    },
    BLUE("蓝色") {
        public void printColor() {
            System.out.println("这是天空的颜色:"+name);
        }
    };

    String name;

    ColorEnum2(String name) {
        this.name = name;
    }
}
  • 使用如下:
    @Test
    public void testColorInfo(){
        for(ColorInfo ci : ColorEnum2.values()){
            ci.printColor();
        }
    }

这里写图片描述

Enum类中定义抽象方法

  • 当然我们还可以直接在Enum类中定义抽象方法:
public enum ColorEnum3 {
    RED("红色") {
        public void printColor() {
            isRed();
        }

        public void isRed(){
            System.out.println("这是一个红色");
        }
    },
    GREEN("绿色") {
        public void printColor() {
            System.out.println("最富有活力的颜色:" + name);
        }
    },
    BLUE("蓝色") {
        public void printColor() {
            System.out.println("这是天空的颜色:" + name);
        }
    };

    String name;

    ColorEnum3(String name) {
        this.name = name;
    }

    public abstract void printColor();

}

和实现接口的效果是一样的。

EnumSet

EnumSet 是一种专门为枚举类型所设计的 Set 类型。

HashSet相比,由于使用了内部位向量表示,因此它是特定 Enum 常量集的非常有效且紧凑的表示形式。

它提供了类型安全的替代方法,以替代传统的基于int的“位标志”,使我们能够编写更易读和易于维护的简洁代码。

EnumSet 是抽象类,其有两个实现:RegularEnumSetJumboEnumSet,选择哪一个取决于实例化时枚举中常量的数量。

在很多场景中的枚举常量集合操作(如:取子集、增加、删除、containsAllremoveAll批操作)使用EnumSet非常合适;如果需要迭代所有可能的常量则使用Enum.values()

    @Test
    public void testEnumSet() {
        EnumSet<ColorEnum3> enumSet = EnumSet.allOf(ColorEnum3.class);
        Iterator<ColorEnum3> iterator = enumSet.iterator();
        while(iterator.hasNext()){
            iterator.next().printColor();
        }
    }

EnumMap

EnumMap是一个专门化的映射实现,用于将枚举常量用作键。与对应的 HashMap 相比,它是一个高效紧凑的实现,并且在内部表示为一个数组:

    @Test
	public void testEnumMap(){
    	EnumMap<ColorEnum3,String> enumMap = new EnumMap(ColorEnum3.class);
    	enumMap.put(ColorEnum3.RED,"红色");
    	enumMap.put(ColorEnum3.GREEN,"绿色");
    	enumMap.put(ColorEnum3.BLUE,"蓝色");
    	for(Map.Entry<ColorEnum3,String> entry : enumMap.entrySet()){
      	  System.out.println(entry.getKey()+"-->"+entry.getValue());
    	}
	}

枚举的常见方法

Enum是所有 Java 语言枚举类型的公共基本类(注意Enum其实是抽象类),以下是它的常见方法:

返回类型方法名称方法说明
intcompareTo(E o)比较此枚举与指定对象的顺序
booleanequals(Object other)当指定对象等于此枚举常量时,返回 true。
Class<?>getDeclaringClass()返回与此枚举常量的枚举类型相对应的 Class 对象
Stringname()返回此枚举常量的名称,在其枚举声明中对其进行声明
intordinal()返回枚举常量的序数(它在枚举声明中的位置,其中初始常量序数为零)
StringtoString()返回枚举常量的名称,它包含在声明中
static<T extends Enum> Tstatic valueOf(Class enumType, String name)返回带指定名称的指定枚举类型的枚举常量。

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;
    }
    
    //Enum类内部默认生成该构造函数,且只能被编译器调用,我们是无法手动操作的
    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);
    }
    
      /**
     * 枚举类不能重载finalize方法
     */
    protected final void finalize() { }
/**
 * 抛出CloneNotSupportedException。 
 * 这保证了枚举永远不会被克隆,这是保持其"单身"状态所必需的。
 */
protected final Object clone() throws CloneNotSupportedException {
    throw new CloneNotSupportedException();
}
    /**
     * 防止默认反序列化
     */
    private void readObject(ObjectInputStream in) throws IOException,
        ClassNotFoundException {
        throw new InvalidObjectException("can't deserialize enum");
    }

    private void readObjectNoData() throws ObjectStreamException {
        throw new InvalidObjectException("can't deserialize enum");
    }

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

枚举与设计模式

枚举实现单例模式

使用枚举单例的写法,我们完全不用考虑线程安全、序列化和反射的问题。枚举线程安全、序列化是由jvm保证的,每一个枚举类型和定义的枚举变量在JVM中都是唯一的,在枚举类型的序列化和反序列化上,Java做了特殊的规定:在序列化时Java仅仅是将枚举对象的name属性输出到结果中,反序列化的时候则是通过java.lang.Enum的valueOf方法来根据名字查找枚举对象。同时,编译器是不允许任何对这种序列化机制的定制的并禁用了writeObject、readObject、readObjectNoData、writeReplace和readResolve等方法,从而保证了枚举实例的唯一性。

而且《Effective Java 》和《Java与模式》都非常推荐这种方式

《Effective Java 中文版 第二版》

这种方法在功能上与公有域方法相近,但是它更加简洁,无偿提供了序列化机制,绝对防止多次实例化,即使是在面对复杂序列化或者反射攻击的时候。虽然这种方法还没有广泛采用,但是单元素的枚举类型已经成为实现 Singleton的最佳方法。

《Java与模式》中,作者这样写道,

使用枚举来实现单实例控制会更加简洁,而且无偿地提供了序列化机制,并由JVM从根本上提供保障,绝对防止多次实例化,是更简洁、高效、安全的实现单例的方式。

public enum EnumSingleton {

    SINGLETON;
    public  static EnumSingleton getDemo(){
        return SINGLETON;
    }
}

枚举实现策略模式

通常,策略模式由不同类实现同一个接口来实现的。

这也就意味着添加新策略意味着添加新的实现类。使用枚举,可以轻松完成此任务,添加新的实现意味着只定义具有某个实现的另一个实例。

下面的代码段显示了如何使用枚举实现策略模式:

public enum ShowColorStrategy {
    EXPRESS {
        @Override
        public void show(ColorEnum ce) {
            System.out.println("color is " + ce.name());
        }
    },
    NORMAL {
        @Override
        public void show(ColorEnum ce) {
            System.out.println("color's number :" + ce.ordinal());
        }
    };

    public abstract void show(ColorEnum ce);
}

枚举与JSON

有关枚举类型的JSON序列化/反序列化(包括自定义)的更多信息,请参阅Jackson-将枚举序列化为JSON对象。这个快速教程将展示如何控制使用Jackson 2Java Enums进行序列化和反序列化的方式 –转至jackson主要教程

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值