一、枚举类型是什么?
JDK5引入了一种新特性,关键字enum可以将一组具名的值的有限集合创建为一种新的类型,而这些具名的值可以作为常规的程序组件使用,这就是枚举类型。
一个枚举的简单例子
enum SeasonEnum {
//每一个被称为枚举项,它们都是本类的实例,该类就有4个实例
SPRING,SUMMER,FALL,WINTER;
}
二、 枚举类的常用方法
Enum常用方法有以下几种:
- name(); 返回enum实例声明时的名字。
- ordinal(); 返回一个int值,表示enum实例在声明的次序。
- equals(); 返回布尔值,enum实例判断相等
- compareTo() 比较enum实例与指定对象的顺序
- values(); 返回enum实例的数组
- valueOf(String name) 由名称获取枚举类中定义的常量
直接看例子吧:
enum Shrubbery {
GROUND, CRAWLING, HANGING
}
public static class EnumClassTest {
public static void main(String[] args) {
//values 返回enum实例的数组
for (Shrubbery temp : Shrubbery.values()) {
// name 返回实例enum声明的名字
System.out.println("名称:" + temp.name());
// ordinal 返回实例enum属性的索引
System.out.println("索引:" + temp.ordinal());
// equals 返回比较的布尔值
System.out.println("比较结果:" + Shrubbery.CRAWLING.equals(temp));
System.out.println("********************************************");
}
//由名称获取枚举类中定义的常量值
System.out.println(Shrubbery.valueOf("CRAWLING"));
}
}
运行结果:
名称:GROUND
序号:0
比较结果:false
比较结果:false
********************************************
名称:CRAWLING
序号:1
比较结果:true
比较结果:true
********************************************
名称:HANGING
序号:2
比较结果:false
比较结果:false
********************************************
CRAWLING
三、枚举类的使用场景及其优点
1、枚举优点:
从下面枚举类字节码分析,我们知道:
- 一个枚举类是被final关键字修饰的,不能被继承。
- 并且它的变量都是public static final修饰的,都是静态变量。
当一个Java类第一次被真正使用到的时候静态资源被初始化、Java类的加载和初始化过程都是线程安全的。 它可以增强代码的可读性,可维护性,同时,它也具有安全性。 假设现在有这样的业务场景:订单完成后,通知买家评论。很容易有以下代码:
//订单已完成
if(3==orderStatus){
//do something
}
很显然,这段代码出现了魔法数,如果你没写注释,谁知道3表示订单什么状态呢,不仅阅读起来比较困难,维护起来也很蛋疼?如果使用枚举类呢,如下:
public enum OrderStatusEnum {
//在枚举常量后面必须添加分号,因为在枚举常量后面还有其他成员时,分号是必须的。
//枚举常量必须在枚举类中所有成员的上方声明。
UNPAID(0, "未付款"), PAID(1, "已付款"), SEND(2, "已发货"), FINISH(3, "已完成"),;
//成员变量
private int index;
private String desc;
public int getIndex() {
return index;
}
public String getDesc() {
return desc;
}
//构造方法
//枚举类的构造器不可以添加访问修饰符,枚举类的构造器默认是private的。但你自己不能添加private来修饰构造器
OrderStatusEnum(int index, String desc) {
this.index = index;
this.desc = desc;
}
}
//订单已完成
if(OrderStatusEnum.FINISH.getIndex()==orderStatus){
//do something
}
可见,枚举类让这段代码可读性更强,也比较好维护,后面加个新的订单状态,直接添加多一种枚举状态就可以了。有些朋友认为,public static final int这种静态常量也可以实现该功能呀,如下:
public class OrderStatus {
//未付款
public static final int UNPAID = 0;
public static final int PAID = 1;
public static final int SENDED = 2;
public static final int FINISH = 3;
}
//订单已完成
if(OrderStatus.FINISH==orderStatus){
//do something
}
当然,静态常量这种方式实现,可读性是没有任何问题的,日常工作中代码这样写也无可厚非。但是,定义int值相同的变量,容易混淆,如你定义PAID和SENDED状态都是2,编译器是不会报错的。
因此,枚举类第一个优点就是可读性,可维护性都不错,所以推荐。
2、枚举的使用场景
enum组织常量
在JDK5之前,常量定义都是这样,先定义一个类或者接口,属性类型都是public static final...,有了枚举之后,可以把常量组织到枚举类了,如下:
enum SeasonEnum {
SPRING,SUMMER,FALL,WINTER,;
}
enum与switch 环环相扣
一般来说,switch-case中只能使用整数值,但是枚举实例天生就具备整数值的次序,因此,在switch语句中是可以使用enum的,如下:
enum OrderStatusEnum {
UNPAID, PAID, SEND, FINISH
}
public class OrderStatusTest {
public static void main(String[] args) {
changeByOrderStatus(OrderStatusEnum.FINISH);
}
static void changeByOrderStatus(OrderStatusEnum orderStatusEnum) {
switch (orderStatusEnum) {
case UNPAID:
System.out.println("老板,你下单了,赶紧付钱吧");
break;
case PAID:
System.out.println("我已经付钱啦");
break;
case SENDED:
System.out.println("已发货");
break;
case FINISH:
System.out.println("订单完成啦");
break;
}
}
}
在日常开发中,enum与switch一起使用,会让你的代码可读性更好哦。
向枚举中添加新的方法
可以向枚举类添加新方法的,如get方法,普通方法等,以下是日常工作最常用的一种枚举写法:
public enum OrderStatusEnum {
UNPAID(0, "未付款"), PAID(1, "已付款"), SENDED(2, "已发货"), FINISH(3, "已完成"),;
//成员变量
private int index;
private String desc;
//get方法
public int getIndex() {
return index;
}
public String getDesc() {
return desc;
}
//构造器方法
OrderStatusEnum(int index, String desc) {
this.index = index;
this.desc = desc;
}
//普通方法
public static OrderStatusEnum of(int index){
for (OrderStatusEnum temp : values()) {
if (temp.getIndex() == index) {
return temp;
}
}
return null;
}
}
枚举实现接口
所有枚举类都继承于java.lang.Enum,所以枚举不能再继承其他类了。但是枚举可以实现接口呀,这给枚举增添了不少色彩。如下:
public interface ISeasonBehaviour {
void showSeasonBeauty();
String getSeasonName();
}
public enum SeasonEnum implements ISeasonBehaviour {
SPRING(1,"春天"),SUMMER(2,"夏天"),FALL(3,"秋天"),WINTER(4,"冬天"),
;
private int index;
private String name;
SeasonEnum(int index, String name) {
this.index = index;
this.name = name;
}
public int getIndex() {
return index;
}
public String getName() {
return name;
}
//接口方法
@Override
public void showSeasonBeauty() {
System.out.println("welcome to " + this.name);
}
//接口方法
@Override
public String getSeasonName() {
return this.name;
}
}
使用接口组织枚举
public interface Food {
enum Coffee implements Food{
BLACK_COFFEE,DECAF_COFFEE,LATTE,CAPPUCCINO
}
enum Dessert implements Food{
FRUIT, CAKE, GELATO
}
}
四、枚举实现的单例
effective java提过,最佳的单例实现模式就是枚举模式。单例模式的实现有好几种方式,为什么是枚举实现的方式最佳呢?
因为枚举实现的单例有以下优点:
- 枚举单例写法简单
- 枚举可解决线程安全问题
- 枚举可解决反序列化会破坏单例的问题
一个枚举单例demo如下:
public class SingletonEnumTest {
public enum SingletonEnum {
INSTANCE,;
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public static void main(String[] args) {
SingletonEnum.INSTANCE.setName("jay@huaxiao");
System.out.println(SingletonEnum.INSTANCE.getName());
}
}
五、EnumSet 和EnumMap
EnumSet
先来看看EnumSet的继承体系图
显然,EnumSet也实现了set接口,相比于HashSet,它有以下优点:
- 消耗较少的内存
- 效率更高,因为是位向量实现的。
- 可以预测的遍历顺序(enum常量的声明顺序)
- 拒绝加null
EnumSet就是set的高性能实现,它的要求就是存放必须是同一枚举类型。 EnumSet的常用方法:
- allof() 创建一个包含指定枚举类里所有枚举值的EnumSet集合
- range() 获取某个范围的枚举实例
- of() 创建一个包括参数中所有枚举元素的EnumSet集合
- complementOf() 初始枚举集合包括指定枚举集合的补集
看实例,最实际:
public class EnumTest {
public static void main(String[] args) {
// Creating a set
EnumSet<SeasonEnum> set1, set2, set3, set4;
// Adding elements
set1 = EnumSet.of(SeasonEnum.SPRING, SeasonEnum.FALL, SeasonEnum.WINTER);
set2 = EnumSet.complementOf(set1);
set3 = EnumSet.allOf(SeasonEnum.class);
set4 = EnumSet.range(SeasonEnum.SUMMER,SeasonEnum.WINTER);
System.out.println("Set 1: " + set1);
System.out.println("Set 2: " + set2);
System.out.println("Set 3: " + set3);
System.out.println("Set 4: " + set4);
}
}
输出结果:
Set 1: [SPRING, FALL, WINTER]
Set 2: [SUMMER]
Set 3: [SPRING, SUMMER, FALL, WINTER]
Set 4: [SUMMER, FALL, WINTER]
EnumMap
EnumMap的继承体系图如下:
EnumMap也实现了Map接口,相对于HashMap,它也有这些优点:
- 消耗较少的内存
- 效率更高
- 可以预测的遍历顺序
- 拒绝null
EnumMap就是map的高性能实现。 它的常用方法跟HashMap是一致的,唯一约束是枚举相关。
看实例,最实际:
public class EnumTest {
public static void main(String[] args) {
Map<SeasonEnum, String> map = new EnumMap<>(SeasonEnum.class);
map.put(SeasonEnum.SPRING, "春天");
map.put(SeasonEnum.SUMMER, "夏天");
map.put(SeasonEnum.FALL, "秋天");
map.put(SeasonEnum.WINTER, "冬天");
System.out.println(map);
System.out.println(map.get(SeasonEnum.SPRING));
}
}
运行结果
{SPRING=春天, SUMMER=夏天, FALL=秋天, WINTER=冬天}
春天
六、深入研究枚举类
枚举类型到底是什么类呢?我们新建一个简单枚举例子,看看它的庐山真面目。如下:
public enum Shrubbery {
GROUND,CRAWLING, HANGING
}
使用javac编译上面的枚举类,可得Shrubbery.class文件。
javac Shrubbery.java
再用javap命令,反编译得到字节码文件。如:执行javap Shrubbery.class可到以下字节码文件。
Compiled from "Shrubbery.java"
public final class enumtest.Shrubbery extends java.lang.Enum<enumtest.Shrubbery> {
public static final enumtest.Shrubbery GROUND;
public static final enumtest.Shrubbery CRAWLING;
public static final enumtest.Shrubbery HANGING;
public static enumtest.Shrubbery[] values();
public static enumtest.Shrubbery valueOf(java.lang.String);
static {};
}
从字节码文件可以发现:
- Shrubbery枚举变成了一个final修饰的类,也就是说,它不能被继承啦。
- Shrubbery是java.lang.Enum的子类。
- Shrubbery定义的枚举值都是public static final修饰的,即都是静态常量。
为了看得更仔细,javap反编译加多个参数-c,执行如下命令:
javap -c Shrubbery.class
静态代码块的字节码文件如下:
static {};
Code:
0: new #4 // class enumtest/Shrubbery
3: dup
4: ldc #7 // String GROUND
6: iconst_0
7: invokespecial #8 // Method "<init>":(Ljava/lang/String;I)V
10: putstatic #9 // Field GROUND:Lenumtest/Shrubbery;
13: new #4 // class enumtest/Shrubbery
16: dup
17: ldc #10 // String CRAWLING
19: iconst_1
20: invokespecial #8 // Method "<init>":(Ljava/lang/String;I)V
23: putstatic #11 // Field CRAWLING:Lenumtest/Shrubbery;
26: new #4 // class enumtest/Shrubbery
29: dup
30: ldc #12 // String HANGING
32: iconst_2
33: invokespecial #8 // Method "<init>":(Ljava/lang/String;I)V
36: putstatic #13 // Field HANGING:Lenumtest/Shrubbery;
39: iconst_3
40: anewarray #4 // class enumtest/Shrubbery
43: dup
44: iconst_0
45: getstatic #9 // Field GROUND:Lenumtest/Shrubbery;
48: aastore
49: dup
50: iconst_1
51: getstatic #11 // Field CRAWLING:Lenumtest/Shrubbery;
54: aastore
55: dup
56: iconst_2
57: getstatic #13 // Field HANGING:Lenumtest/Shrubbery;
60: aastore
61: putstatic #1 // Field $VALUES:[Lenumtest/Shrubbery;
64: return
}
- 0-39行实例化了Shrubbery枚举类的GROUND,CRAWLING, HANGING;
- 40-64为创建Shrubbery[]数组$VALUES,并将上面的三个实例化对象放入数组的操作。
- 因此,枚举类方法values()返回enum枚举实例的数组是不是豁然开朗啦。
来源于:
作者:捡田螺的小男孩
链接:juejin.cn/post/684490…
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。