java枚举类及面试题为什么枚举实现单例模式是安全的?

枚举类

为什么会有枚举类?

假如说程序中有一些颜色的状态,或者说消息的类型等,在JDK1.5之前,只能用常量来进行表示

public class TestEnum {
    public static final int RED = 1;
    public static final int BLACK = 2;
    public static final int GREEN = 3;
    public static void main(String[] args) {
        int num = 1;
        System.out.println(RED == num);
    }
}
//true

num不是一个颜色,就与实际不太相符合,于是JDK1.5中引入了枚举类型

enum EnumColor {
    RED, BLACK, GREEN;

    public static void main(String[] args) {
        EnumColor color = EnumColor.BLACK;
        switch (color) {
            case RED:
                System.out.println("红色");
                break;
            case BLACK:
                System.out.println("黑色");
                break;
            case GREEN:
                System.out.println("绿色");
                break;
        }
    }
}
//黑色

枚举类的使用

  • 类的对象只有有限个,确定的(星期,性别,季节…)
  • 当需要定义一组常量
  • 使用enum定义的枚举类默认继承了java.lang. Enum类,因此不能再继承其他类
  • 枚举类的构造器只能使用private权限修饰符
  • 枚举类的所有实例必须在枚举类中显式列出(,分隔结尾)。列出的实例
    系统会自动添加public static final修饰必须在枚举类的第一行声明枚举类对象
  • 枚举不能再类外直接实例化,也不能被继承

如果枚举有参数可以添加对应的构造函数,但要注意:枚举的构造函数默认是私有的。所以不能被继承
枚举就是定义了一些状态或者常见集合,一般不需要单独实例化,用的时候到枚举类中找合适的即可
枚举的构造函数不能使用public和protected修饰。

枚举类的常用方法

方法名称 描述
values() 以数组形式返回枚举类型的所有成员
ordinal() 获取枚举成员的索引位置
valueOf() 将普通字符串转换为枚举实例
compareTo() 比较两个枚举成员在定义时的顺序
enum EnumColor {
    RED, BLACK, GREEN;
    public static void main(String[] args) {
    // 以数组的方式返回所有的枚举成员
        EnumColor[] colors = EnumColor.values();
    // 打印每个枚举成员的索引
        for(int i = 0; i < colors.length; ++i){
            System.out.println(colors[i] + ":" + colors[i].ordinal());
        }
    // 将字符串GREEN转化为枚举类型
        EnumColor color1 = EnumColor.valueOf("GREEN");
        System.out.println(color1);
    // 在进行转换时,如果有对应的枚举类型则转换,否则抛出IllegalArgumentException
//     EnumColor color2 = EnumColor.valueOf("YELLOW");//定义的枚举类没有YELLOW
//     System.out.println(color2);
        EnumColor color2 = EnumColor.valueOf("BLACK");//定义的枚举类没有YELLOW
        System.out.println(color2);
        System.out.println("-------------------------------------");
        System.out.println("枚举实例的比较");
    // 注意此处的比较是使用枚举成员的索引来比较了
        EnumColor black = EnumColor.BLACK;
        EnumColor red = EnumColor.RED;
        System.out.println(black.compareTo(red));
        System.out.println(BLACK.compareTo(RED));
        System.out.println(RED.compareTo(BLACK));
    }
    //结果:
    RED:0
	BLACK:1
	GREEN:2
	GREEN
	BLACK
	-------------------------------------
	枚举实例的比较
	1
	1
	-1

枚举的构造

上述枚举类型有一个不太友好的地方是,枚举中只有枚举常量,拿到一个枚举常量后还是不知道其是什么颜色的,因此可以给枚举增加构造函数。

 enum EnumColor {
    RED("红色", 1), BLACK("黑色", 2), GREEN("绿色", 3);
    private String color;
    private int key;
    // 注意:枚举的构造函数默认是私有的,此处不能增加非private的访问权限进行限制
    /*public*/EnumColor(String color, int key){
        this.color = color;
        this.key = key;
    }
    public static EnumColor getEnum(String str){
        for (EnumColor color : EnumColor.values()){
            if(color.color.equals(str))
                return color;
        }
        return null;
    }
    public static void main(String[] args) {
        System.out.println(EnumColor.getEnum("红色"));
    }
}

枚举类型能被反射吗?

不能不能不能!
通过看反射的源码,可以看出反射在通过newInstance创建对象时,会检查该类是否ENUM修饰,如果是则抛出异常,反射失败。
在这里插入图片描述

为什么枚举实现单例模式是安全的?

enum Singleton5 {//枚举类的单例模式
    INSTANCE;
    public Singleton5 getInstance(){
        return INSTANCE;
    }
}

1、 私有化构造器并不保险,通过反射机制调用私有构造器。而枚举类不能被反射,所以可以防止反射攻击

//模拟反射攻击
class Singleton {//双重校验锁,性能佳线程安全
    private static Singleton4 instance=null;
    private Singleton4() {}
    public static Singleton4 getInstance() {
        if(instance==null) {
            synchronized (Singleton4.class) {
                if (instance==null) {
                    //new对象分为三个操作
                    //分配内存
                    //调用构造方法初始化
                    //赋值
                    instance=new Singleton4();
                }
            }
        }
        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));
}
//com.lxp.pattern.singleton.Singleton@1540e19d
//com.lxp.pattern.singleton.Singleton@1540e19d
//.lxp.pattern.singleton.Singleton@677327b6
//正常情况下,实例化两个实例是否相同:true
//通过反射攻击单例模式情况下,实例化两个实例是否相同:false

2.避免序列化问题(任何一个readObject方法,不管是显式的还是默认的,它都会返回一个新建的实例,这个新建的实例不同于该类初始化时创建的实例)

枚举类的优缺点

1、优点

枚举常量更简单安全 。
枚举具有内置方法 ,代码更优雅

2. 缺点

不可继承,无法扩展

猜你喜欢

转载自blog.csdn.net/qq_41552331/article/details/106293141