开启掘金成长之旅**!这是我参与「掘金日新计划 · 2 月更文挑战」的第 19天,点击查看活动详情**
1.枚举
枚举的主要用途是:将常量组织起来统一进行管理
本质:是 java.lang.Enum 的子类,也就是说,自己写的枚举类,就算没有显示的继承 Enum ,但是其默认继承了 这个抽象枚举类,所以枚举类是不能实例化的
编辑
1.1枚举的使用
1.1.1switch语句
public enum TestEnum {
RED,GREEN,BACK;
public static void main(String[] args) {
TestEnum testEnum2 = TestEnum.BACK;
switch (testEnum2){
case RED:
System.out.println("RED");
break;
case BACK:
System.out.println("BACK");
break;
case GREEN:
System.out.println("GREEN");
break;
default:
break;
}
}
}
编辑
1.1.2Enum 类的常用方法
方法名称
描述
values()
以数组形式返回枚举类型的所有成员
ordinal()
获取枚举成员的索引位置
valueOf()
将普通字符串转换为枚举实例
compareTo()
比较两个枚举成员在定义时的顺序
values()与ordinal()的使用
public static void main(String[] args) {
TestEnum[] testEnums = TestEnum.values();
for (int i = 0; i < testEnums.length; i++) {
System.out.println(testEnums[i]+"ori:"+testEnums[i].ordinal());
}
}
编辑
valueOf()
public static void main(String[] args) {
TestEnum testEnum = TestEnum.valueOf("RED");
System.out.println(testEnum);
}
编辑
compareTo()
public static void main(String[] args) {
System.out.println(RED.compareTo(BACK));
}
编辑
枚举的构造方法默认是私有的
编辑
1.2枚举的优缺点
优点:
1. 枚举常量更简单安全
2. 枚举具有内置方法 ,代码更优雅
缺点:
1. 不可继承,无法扩展
2.枚举与反射
在反射中,任意一个类,他的构造方法就算是私有的,我们也可以通过反射获取实例对象,上述枚举类的构造方法也是私有的,我们是否可以通过反射建立它的实例对象?
public enum TestEnum {
RED("RED",1),GREEN("GREEN",2),BACK("BACK",3);
private String color;
private int val;
TestEnum(String color,int val){
this.color = color;
this.val = val;
}
public static void reflectPrivateConstructor() {
try {
Class<?> classStudent = Class.forName("TestEnum");
//注意传入对应的参数,获得对应的构造方法来构造对象,当前枚举类是提供了两个参数分别是String和int。
Constructor<?> declaredConstructorStudent =
classStudent.getDeclaredConstructor(String.class,int.class);
//设置为true后可修改访问权限
declaredConstructorStudent.setAccessible(true);
//这里为了凑齐参数,后两个参数随便给,不给也行,默认为空值
Object objectStudent = declaredConstructorStudent.newInstance("黑色",123);
TestEnum testEnum = (TestEnum) objectStudent;
System.out.println("获得枚举的私有构造函数:"+testEnum);
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
编辑
异常信息说没有对应的构造方法!!我们提供的枚举类型就是String color这个参数,已经通过Constructor调用了,没有对应的构造方法原因是:枚举类型默认继承于java.lang.Enum,子类要帮助父类进行构造,然而我们并没有进行帮助构造,枚举类比较特殊,不能用super帮助构造
虽然我们只写了一个参数,但是还默认添加了两个参数,看源码:
编辑
也就是说,我们自己的构造函数参数是String和int,同时他默认后边还会给两个参数,一个是 String一个是int.也就是说,这里我们正确给的是4个参数,我们修改一下
编辑
修改后:编辑
出现另一个异常信息:不能反射创建枚举对象!!
我们查看一下newInstance源码:
编辑
枚举在这里被过滤了,因此不能通过反射获取枚举类的实例!
原版面试问题是:为什么枚举实现单例模式是安全的?
3 面试:为什么枚举实现单例模式是安全的?
私有化构造器并不保险,通过反射机制调用私有构造器。而枚举类不能被反射,所以可以防止反射攻击,但是其他的单例模式被反射攻击后会创建出不同的实例,导致线程不安全!
模拟一个反射攻击
//模拟反射攻击
class Singleton {
private static Singleton instance=null;
private Singleton() {}
public static Singleton getInstance() {
if(instance==null) {
synchronized (Singleton.class) {
if (instance==null) {
//new对象分为三个操作
//分配内存
//调用构造方法初始化
//赋值
instance=new Singleton();
}
}
}
return instance;
}
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Singleton s = Singleton.getInstance();
Singleton sUsual = Singleton.getInstance();
Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor();
constructor.setAccessible(true);
Singleton sReflection = constructor.newInstance();
System.out.println(s + "\n" + sUsual + "\n" + sReflection);
System.out.println("正常情况下,实例化两个实例是否相同:" + (s == sUsual));
System.out.println("通过反射攻击单例模式情况下,实例化两个实例是否相同:" + (s == sReflection));
}
}
如果通过反射创建出的实例和正常创建出的实例比较后是不相同的,那么就会出现线程安全问题!
编辑
我们看到私有的构造器也并不保险,通过反射攻击后,还是出现了线程安全问题,但是枚举类不能通过反射创建,能做到线程安全!