文章目录
枚举介绍
枚举是Java中有着各种特殊约束的类的集合,但是这个类被严重的约束着,但也正是这些特殊的约束才使得枚举成为枚举。
Enum是java.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
是抽象类,其有两个实现:RegularEnumSet
、JumboEnumSet
,选择哪一个取决于实例化时枚举中常量的数量。
在很多场景中的枚举常量集合操作(如:取子集、增加、删除、containsAll
和removeAll
批操作)使用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其实是抽象类),以下是它的常见方法:
返回类型 | 方法名称 | 方法说明 |
---|---|---|
int | compareTo(E o) | 比较此枚举与指定对象的顺序 |
boolean | equals(Object other) | 当指定对象等于此枚举常量时,返回 true。 |
Class<?> | getDeclaringClass() | 返回与此枚举常量的枚举类型相对应的 Class 对象 |
String | name() | 返回此枚举常量的名称,在其枚举声明中对其进行声明 |
int | ordinal() | 返回枚举常量的序数(它在枚举声明中的位置,其中初始常量序数为零) |
String | toString() | 返回枚举常量的名称,它包含在声明中 |
static<T extends Enum> T | static 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 2对Java Enums进行序列化和反序列化的方式 –转至jackson主要教程。