我们先来写一个枚举
public enum TestEnum {
//TEACHER 老师;STUDENT 学生;PARENT 父母
TEACHER,STUDENT,PARENT
}
然后编译成class
文件,再反编译class
文件。
PS E:\Study\code\demo\src\main\java\com\lvshen\demo\enumtest> javac .\TestEnum.java
PS E:\Study\code\demo\src\main\java\com\lvshen\demo\enumtest> javap -p .\TestEnum.class
Compiled from "TestEnum.java"
//代码区
public final class com.lvshen.demo.enumtest.TestEnum extends java.lang.Enum {
public static final com.lvshen.demo.enumtest.TestEnum TEACHER;
public static final com.lvshen.demo.enumtest.TestEnum STUDENT;
public static final com.lvshen.demo.enumtest.TestEnum PARENT;
private static final com.lvshen.demo.enumtest.TestEnum[] $VALUES;
public static com.lvshen.demo.enumtest.TestEnum[] values();
public static com.lvshen.demo.enumtest.TestEnum valueOf(java.lang.String);
//构造函数
private com.lvshen.demo.enumtest.TestEnum();
static {};
}
我们发现枚举有这几个特性:
“(1)枚举
TestEnum
被final
修饰,并且默认继承了Enum
类。因此不能再继承其他的类。(2)枚举的构造函数是
private
修饰的,所以不能通过构造函数获取对象。(3)枚举的属性是
static
修饰的,可以通过枚举直接调用属性。(4)
”valueOf(java.lang.String)
可以通过枚举的名称获取对应的实例。
还有一个重要的特性,对于(2)虽然不能直接获取构造对象,你可能会有疑问,我反射暴力获取可以吗?答案是不可以。我们看看反射是怎么做的。
Class> aClass = Class.forName("xx.xx.xx");
Constructor> constructor = aClass.getDeclaredConstructor(String.class);
TestEnum test = (TestEnum) constructor.newInstance("");
我们来看看newInstance
。
if ((clazz.getModifiers() & Modifier.ENUM) != 0)
throw new IllegalArgumentException("Cannot reflectively create enum objects");
如果是枚举,你想暴力反射获取对象,直接抛异常。
if/else
当你代码中的if/else
过多的时候,会提高你代码的复杂度,如果你们公司对sonar异味有要求,肯定被if/else
困扰过。
我们可以用枚举消除if/else
,使用场景如下:
if ("PANDA".equals(type)) {
System.out.println("吃竹子");
} else if("CAT".equals(type)) {
System.out.println("吃鱼");
} else if("MONKEY".equals(type)) {
System.out.println("吃香蕉");
}
当输入type
,输出对应结果。例如当type = "CAT"
时,输出"吃鱼"。你肯定会想到用上面的if/else
方法。我们用枚举怎么做呢?
首先创建接口,定义通用方法
public interface Common {
//吃
String eat();
}
创建枚举实现这个方法
public enum AnimalEnum implements Common {
PANDA {
@Override
public String eat() {
return "吃竹子";
}
},
CAT {
@Override
public String eat() {
return "吃鱼";
}
},
MONKEY{
@Override
public String eat() {
return "吃香蕉";
}
}
}
然后调用
String type = "CAT";
String eat = AnimalEnum.valueOf(type).eat();
System.out.println(eat);
当type = "CAT"
时,输出"吃鱼"。至此我们消除了if/else
。
《 Effective Java》作者大力推荐的方式。
这种方式不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化和反射攻击重新创建新的对象,绝对防止多次实例化。
我们来看代码
public enum EnumSingleton {
INSTANCE;
public EnumSingleton getInstance() {
return INSTANCE;
}
}
我们起两个线程来测试
public void test1() {
EnumSingleton instance1 = EnumSingleton.INSTANCE.getInstance();
EnumSingleton instance2 = EnumSingleton.INSTANCE.getInstance();
System.out.println(instance1 == instance2);
}
运行结果为True
。
由之前的反编译可知,属性INSTANCE
被声明为static
的。枚举实现实例化时是线程安全。
Java 规范中规定,每一个枚举类型及其定义的枚举变量在 JVM 中都是唯一的,并且在枚举类型的序列化和反序列化上,Java 做了特殊的规定。在序列化的时候 Java 仅仅是将枚举对象的 name 属性输出到结果中,反序列化的时候则是通过 java.lang.Enum
的 valueOf()
方法来根据名字查找枚举对象,因此反序列化后的实例也会和之前被序列化的对象实例相同。
不过个人觉得想要实现单例就要将这个对象设计成枚举类型的,虽然安全可靠,但还是不优雅。
如果你需要存储key-value格式的数据,并且这个key来源于一个枚举类,那么使用EnumMap
而不是HashMap
。EnumMap
拥有更优良的性能。
public enum TestEnum {
TEACHER,STUDENT,PARENT
}
//使用
EnumMap enumMap = new EnumMap<>(TestEnum.class);
enumMap.put(TestEnum.TEACHER,"教书");
enumMap.put(TestEnum.STUDENT,"学习");
enumMap.put(TestEnum.PARENT,"培育小孩");
String result = enumMap.get(TestEnum.PARENT);
System.out.println(result);
我们可以通过EnumMap
给枚举的成员属性赋予特定行为。如上代码,result = "培育小孩"
。
public enum TestEnum {
TEACHER,STUDENT,PARENT
}
我们定义了枚举TestEnum
,如何获取所有成员属性?这是可以使用EnumSet
。
EnumSet enumSet = EnumSet.allOf(TestEnum.class);
System.out.println(enumSet);
控制台打印
[TEACHER, STUDENT, PARENT]
与HashSet
相比,EnumSet
性能更优良。
我写出这样干净的代码,老板直夸我
云南丽江旅游攻略
使用ThreadLocal怕内存泄漏?
Java进阶之路思维导图
程序员必看书籍推荐
3万字的Java后端面试总结(附PDF)
扫码二维码,获取更多精彩。或微信搜Lvshen_9,可后台回复获取资料
1.回复"java" 获取java电子书;
2.回复"python"获取python电子书;
3.回复"算法"获取算法电子书;
4.回复"大数据"获取大数据电子书;
5.回复"spring"获取SpringBoot的学习视频。
6.回复"面试"获取一线大厂面试资料
7.回复"进阶之路"获取Java进阶之路的思维导图
8.回复"手册"获取阿里巴巴Java开发手册(嵩山终极版)
9.回复"总结"获取Java后端面试经验总结PDF版
10.回复"Redis"获取Redis命令手册,和Redis专项面试习题(PDF)
11.回复"并发导图"获取Java并发编程思维导图(xmind终极版)
另:点击【我的福利】有更多惊喜哦。