掌握Java枚举这几个知识点,日常开发就够啦
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了掌握Java枚举这几个知识点,日常开发就够啦相关的知识,希望对你有一定的参考价值。
前言
春节来临之际,祝大家新年快乐哈。整理了Java枚举的相关知识,算是比较基础的,希望大家一起学习进步。
一、枚举类型是什么?
JDK5引入了一种新特性,关键字enum可以将一组具名的值的有限集合创建为一种新的类型,而这些具名的值可以作为常规的程序组件使用,这就是枚举类型。
一个枚举的简单例子
enum
SeasonEnum
{
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
class
EnumClassTest
{
public
static
void
main
(
String
[]
args
)
{
//values 返回enum实例的数组
for
(
Shrubbery
temp
:
Shrubbery
.
values
())
{
// name 返回实例enum声明的名字
System
.
out
.
println
(
temp
.
name
()
+
" ordinal is "
+
temp
.
ordinal
()
+
" ,equal result is "
+
Shrubbery
.
CRAWLING
.
equals
(
temp
)
+
",compare result is "
+
Shrubbery
.
CRAWLING
.
compareTo
(
temp
));
}
//由名称获取枚举类中定义的常量值
System
.
out
.
println
(
Shrubbery
.
valueOf
(
"CRAWLING"
));
}
}
运行结果:
GROUND ordinal
is
0
,
equal result
is
false
,
compare result
is
1
CRAWLING ordinal
is
1
,
equal result
is
true
,
compare result
is
0
HANGING ordinal
is
2
,
equal result
is
false
,
compare result
is
-
1
CRAWLING
三、枚举类的真面目
枚举类型到底是什么类呢?我们新建一个简单枚举例子,看看它的庐山真面目。如下:
public
enum
Shrubbery
{
GROUND
,
CRAWLING
,
HANGING
}
使用javac编译上面的枚举类,可得Shrubbery.class文件。
javac
Shrubbery
.
java
再用javap命令,反编译得到字节码文件。如:执行 javapShrubbery.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枚举实例的数组是不是豁然开朗啦。
四、枚举类的优点
枚举类有什么优点呢?就是我们为什么要选择使用枚举类呢?因为它可以增强代码的可读性,可维护性,同时,它也具有安全性。
枚举类可以增强可读性、可维护性
假设现在有这样的业务场景:订单完成后,通知买家评论。很容易有以下代码:
//订单已完成
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
;
}
OrderStatusEnum
(
int
index
,
String
desc
)
{
this
.
index
=
index
;
this
.
desc
=
desc
;
}
}
//订单已完成
if
(
OrderStatusEnum
.
FINISH
.
getIndex
()==
orderStatus
){
//do something
}
可见,枚举类让这段代码可读性更强,也比较好维护,后面加个新的订单状态,直接添加多一种枚举状态就可以了。有些朋友认为, publicstaticfinalint这种静态常量也可以实现该功能呀,如下:
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,编译器是不会报错的。
因此,枚举类第一个优点就是可读性,可维护性都不错,所以推荐。
枚举类安全性
除了可读性、可维护性外,枚举类还有个巨大的优点,就是安全性。
从上一节枚举类字节码分析,我们知道:
- 一个枚举类是被final关键字修饰的,不能被继承。
- 并且它的变量都是public static final修饰的,都是静态变量。
当一个Java类第一次被真正使用到的时候静态资源被初始化、Java类的加载和初始化过程都是线程安全的。
五、枚举的常见用法
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
}
}
六、枚举类比较是用==还是equals?
先看一个例子,如下:
public
class
EnumTest
{
public
static
void
main
(
String
[]
args
)
{
Shrubbery
s1
=
Shrubbery
.
CRAWLING
;
Shrubbery
s2
=
Shrubbery
.
GROUND
;
Shrubbery
s3
=
Shrubbery
.
CRAWLING
;
System
.
out
.
println
(
"s1==s2,result: "
+
(
s1
==
s2
));
System
.
out
.
println
(
"s1==s3,result: "
+
(
s1
==
s3
));
System
.
out
.
println
(
"Shrubbery.CRAWLING.equals(s1),result: "
+
Shrubbery
.
CRAWLING
.
equals
(
s1
));
System
.
out
.
println
(
"Shrubbery.CRAWLING.equals(s2),result: "
+
Shrubbery
.
CRAWLING
.
equals
(
s2
));
}
}
运行结果:
s1
==
s2
,
result
:
false
s1
==
s3
,
result
:
true
Shrubbery
.
CRAWLING
.
equals
(
s1
),
result
:
true
Shrubbery
.
CRAWLING
.
equals
(
s2
),
result
:
false
可以发现不管用==还是equals,都是可以的。其实枚举的equals方法,就是用==比较的,如下:
public
final
boolean
equals
(
Object
other
)
{
return
this
==
other
;
}
七、枚举实现的单例
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
());
}
}
有关于枚举实现单例,想深入了解的朋友可以看Hollis大神这篇文章,写得真心好!为什么我墙裂建议大家使用枚举来实现单例。
八、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
=冬天}
春天
九、待更新
有关于枚举关键知识点,亲爱的朋友,你有没有要补充的呢?
参考与感谢
- 关于Java中枚举Enum的深入剖析
- 深度分析Java的枚举类型—-枚举的线程安全性及序列化问题
- 为什么我墙裂建议大家使用枚举来实现单例。
- 深入理解Java枚举类型(enum)
- 深入理解 Java 枚举
- EnumSet in Java
- Java 枚举(enum) 详解7种常见的用法
- 《Java编程思想》
个人公众号
- 如果你是个爱学习的好孩子,可以关注我公众号,一起学习讨论。
- 如果你觉得本文有哪些不正确的地方,可以评论,也可以关注我公众号,私聊我,大家一起学习进步哈。
以上是关于掌握Java枚举这几个知识点,日常开发就够啦的主要内容,如果未能解决你的问题,请参考以下文章