枚举类型enum并不是面试里面的一个常考点,但是为什么要给枚举写一篇博客呢,因为我简历上自己在项目中运用了枚举,11月11号在面试的时候,让我写一个枚举,我竟然没有写出来,当时就觉得很不好意思,所以这里将自己对枚举的重新认识记录一下。
一.最简单的枚举例子
enum Status{
NORMAL(1),STOP(0),DELETE(-1);
public Integer getStatusInt() {
return statusInt;
}
private Integer statusInt;
Status(Integer statusInt){
this.statusInt = statusInt;
}
}
写枚举需要注意的地方就是枚举的关键字enum,然后再是首先要申明的枚举常量,在申明的枚举常量之前不能有任何代码,常量一般用大写字母来写,枚举常量之间用逗号分割,最后用分号收尾。用这份代码祭奠我在面试中没有回答上的这道题。
二.枚举的好处
枚举在工程中随处可进,为什么要用枚举呢?枚举是将一些离散值集中起来,用同一个枚举类型来封装他们,做到一处定义,处处使用。
1.安全性
安全性体现在枚举将想要的有限的离散包含进来了,如果要是用这些值只能从枚举里面取,外面随便定义的值不被接受。
2.易于维护,可读性强
NORMAL(1),STOP(0),DELETE(-1);
给学生赋状态值
Student student = new Student();
//通过枚举赋值
student.setStatus(Status.DELETE.getStatusInt());
//通过直接赋值
student.setStatus(-1);
可以看出一个是直接通过枚举赋值,一个是直接赋值,虽然枚举赋值没有直接赋值那么简洁,但是我们一眼就可以看出用枚举赋值是什么意思,而直接赋值的话对于刚接手的人来说没有注解的话就是一头雾水。如果那天这个1被用作表示其他状态值了,还得查询整个项目是修改这个值,如果用的是枚举的话,就只需修改枚举这一块地方就好了
枚举相对于常量的好处
有很多时候枚举和常量可以相互替换,但是枚举的话相比常量会带有更多的信息,我这里说的常量类型是指基础类型和String,枚举里面可以设置多个属性,包含的信息量要多一些,有人可能会疑问我也可以将常量设置成对象,对象里面带多个类型不就好了吗?但是这样的话虽然不能改变常量的引用对象,但是确能改变引用对象的值,所以说也不是很安全。
三.枚举的基本介绍
创建枚举类,其实是在背后偷偷的帮我们继承了Enum这个抽象类的,这个抽象类是我们可以用到的
将网上其他人写的代码拿来一用一下
类聚类型定义
//定义枚举类型
enum Day {
MONDAY, TUESDAY, WEDNESDAY,
THURSDAY, FRIDAY, SATURDAY, SUNDAY
}
对Day进行反编译后会看到这样一个类:
//反编译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
});
}
}
可以看到枚举类其实是一个final类型的普通类,只是这个类继承了Enum抽象类,并且有一个私有的构造方法,在静态代码块里就实例化了这些常量。
看一下Enum这个类给提供了哪些常用方法
返回类型 | 方法名称 | 方法说明 |
---|---|---|
int | compareTo(E o) | 比较此枚举与指定对象的顺序 |
boolean | equals(Object other) | 当指定对象等于此枚举常量时,返回 true。 |
Class<?> | getDeclaringClass() | 返回与此枚举常量的枚举类型相对应的 Class 对象 |
String | name() | 返回此枚举常量的名称,在其枚举声明中对其进行声明 |
int | ordinal() | 返回枚举常量的序数(它在枚举声明中的位置,其中初始常量序数为零) |
String | toString() | 返回枚举常量的名称,它包含在声明中 |
static<T extends Enum<T>> T | static valueOf(Class<T> enumType, String name) |
|
其中还有一个values,用于遍历枚举的,只是这个方法是在编译的时候才会出现在class文件里,不会出现java类中,values用户遍历定义的枚举类型所有常量。values返回的是一个数组。
其中ordinal返回的序数是根据常量摆放的顺序值返回的,不是根据你定义的时候传入值的大小摆放的,如果常量摆放的顺序发生变化,这个值也也会发生变化,定义的常量放在第二位,那么返回的就是1。
public enum EnumWeek {
SUN("7","星期日"),
Mon("1","星期一"),
Tue("2","星期二"),
WIN("3","星期三"),
THU("4","星期四"),
FRI("5","星期五"),
SAT("6","星期六");
private String no;
private String des;
EnumWeek(String no,String des){
this.no=no;
this.des=des;
}
@Override
public String toString(){
return this.no+";"+this.des;
}
public static void main(String[] args) {
System.out.println(FRI.name());
System.out.println(FRI.toString());//和valueOf输出是一样的
System.out.println(EnumWeek.valueOf(SUN.name()));
System.out.println(SUN.compareTo(FRI));
System.out.println(SUN.ordinal());
System.out.println("========遍历输出=======");
for(EnumWeek enumWeek:EnumWeek.values()){
System.out.println(enumWeek.toString());
}
}
}
输出结果:
FRI
5;星期五
7;星期日
-5
0
========遍历输出=======
7;星期日
1;星期一
2;星期二
3;星期三
4;星期四
5;星期五
6;星期六
四.枚举的基本用法
1.作为一个存储离散值的封装类,定义好后,处处使用,这也是枚举最常用的用法。
2.作为switch的参数,switch的参数不仅可以是整数,也可以是字符串,还可以是枚举类型。
enum Status{
NORMAL(1),STOP(0),DELETE(-1);
public Integer getStatusInt() {
return statusInt;
}
private Integer statusInt;
Status(Integer statusInt){
this.statusInt = statusInt;
}
public static void switchTest(Status status){
switch (status){
case STOP:{
System.out.println("暂停");break;
}
case DELETE:{
System.out.println("删除");break;
}
case NORMAL:{
System.out.println("正常");break;
}
}
}
public static void main(String[] args) {
switchTest(Status.DELETE);
}
}
最后输出:删除
3.创建单例
单例模式最重要的就是一个整个堆中只有一个这样的对象,饥汉模式和双重检测都能做到在多线程的情况下创建一个对象,但是当我们使用反射的时候,是可以破坏这个单例模式的,会让单例模式失效。但是使用枚举创建单例的化就不会出现被破坏的情况
下面可以对比看一下
class SingleDoubleCheck{
public static volatile SingleDoubleCheck singleDoubleCheck = null;
private SingleDoubleCheck(){};
public static SingleDoubleCheck getSingleDoubleCheck(){
if(singleDoubleCheck==null){
synchronized (SingleDoubleCheck.class){
if(singleDoubleCheck==null){
singleDoubleCheck = new SingleDoubleCheck();
}
}
}
return singleDoubleCheck;
}
}
enum EnumSingle{
ISTANCE;
/**
* 如果有什么属性的化,在下面定义就好了,我这个相当于是不带任何属性的
*/
}
class Test{
public static void main(String[] args) throws Exception{
SingleDoubleCheck singleDoubleCheck = SingleDoubleCheck.getSingleDoubleCheck();
Class<SingleDoubleCheck> singleDoubleCheckClass = SingleDoubleCheck.class;
Constructor c0= singleDoubleCheckClass.getDeclaredConstructor();
c0.setAccessible(true);
SingleDoubleCheck singleDoubleCheckTwo=(SingleDoubleCheck)c0.newInstance();
System.out.println("SingleDoubleCheck对象:"+singleDoubleCheck+" ;"+singleDoubleCheckTwo.toString());
System.out.println(singleDoubleCheck==singleDoubleCheckTwo);
System.out.println("=================分割线===================");
EnumSingle enumSingle = EnumSingle.ISTANCE;
Class<EnumSingle> enumSingleClass = EnumSingle.class;
Constructor c1 = enumSingleClass.getDeclaredConstructor();
c1.setAccessible(true);
EnumSingle enumSingleTwo = (EnumSingle)c1.newInstance();
System.out.println("EnumSingle对象:"+enumSingle+" ;"+enumSingleTwo.toString());
System.out.println(enumSingle==enumSingleTwo);
}
}
最后输出:可以看出SingleDoubleCheck虽然是双重检测单例模式,但是我们还可以通过反射获取到实例,这就破坏了这个单例模式,而枚举的化如果想用反射来实例是会报错的
SingleDoubleCheck对象:com.example.findwork.normal.SingleDoubleCheck@7d417077 ;com.example.findwork.normal.SingleDoubleCheck@7dc36524
false
=================分割线===================
Exception in thread "main" java.lang.NoSuchMethodException: com.example.findwork.normal.EnumSingle.<init>()
at java.lang.Class.getConstructor0(Class.java:3082)
at java.lang.Class.getDeclaredConstructor(Class.java:2178)
at com.example.findwork.normal.Test.main(EnumWeek.java:141)
五.枚举的集合类型
1.EnumMap
EnumMap存储的都是key是枚举类型的值。
class EnumMapTest{
public static void main(String[] args) {
Map<Status,String> enumMap = new EnumMap(Status.class);
enumMap.put(Status.DELETE,"删除状态");
enumMap.put(Status.STOP,"暂停状态");
enumMap.put(Status.NORMAL,"正常状态");
for(Map.Entry<Status,String> entry:enumMap.entrySet()){
System.out.println("输出结果:"+entry.toString());
}
}
}
输出结果:NORMAL=正常状态
输出结果:STOP=暂停状态
输出结果:DELETE=删除状态
既然有HashMap,为什么要用EnumMap,这是因为EnumMap里面存储结构和HashMap不一样,效率更高效一些,EnumMap里面是两个数组,一个存储Enum类型的key值,一个存储value值,没有链表更没有红黑树,这是因为在实例化EnumMap会传入Enum class对象,这样EnumMap就知道enum有多少常量,有哪些枚举常量,这样就可以实例话key数组,存储值的时候就可以根据enum的序列位知道这个值存在数组的哪一个位置了。