枚举与反射

120 阅读4分钟

开启掘金成长之旅**!这是我参与「掘金日新计划 · 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));
    }
}

如果通过反射创建出的实例和正常创建出的实例比较后是不相同的,那么就会出现线程安全问题!

​编辑

我们看到私有的构造器也并不保险,通过反射攻击后,还是出现了线程安全问题,但是枚举类不能通过反射创建,能做到线程安全!