Java

java枚举由浅及深

2020-04-10  本文已影响0人  lv_shun

结构

public enum SizeEnum {
    SMALL,
    MEDIUM,
    LARGE
}

这是最简单的声明方式。
下面是复杂一些,也是比较普遍使用的方式

public enum SizesEnum {
    SMALL("s", "SMALL"),
    MEDIUM("m", "MEDIUM"),
    LARGE("l", "LARGE");

    private String key;
    private String value;

    public String getKey() {
        return key;
    }

    public String getValue() {
        return value;
    }

    private SizesEnum(String key, String value) {
        this.key = key;
        this.value = value;
    }

    public static SizesEnum getSize(String key) {
        for (SizesEnum size : SizesEnum.values()) {
            if (size.getKey().equals(key)) {
                return size;
            }
        }
        return null;
    }
}

常用属性

public abstract class Enum<E extends Enum<E>>
        implements Comparable<E>, Serializable {
        //枚举名称
        private final String name;
        //枚举顺序
        private final int ordinal;
}

通过源码可知,每个枚举元素都包含了这两个属性,表示枚举字面量和枚举定义的顺序。对应可以通过name()、ordinal()获取。

 String name = SizesEnum.SMALL.name();
 int ordinal = SizesEnum.SMALL.ordinal();

常用方法

除了上面提到的name()、ordinal()方法之外,还可以其他常用的方法

    public final int compareTo(E o) {
        Enum<?> other = (Enum<?>)o;
        Enum<E> self = this;
        if (self.getClass() != other.getClass() && // optimization
            self.getDeclaringClass() != other.getDeclaringClass())
            throw new ClassCastException();
        return self.ordinal - other.ordinal;
    }

除了这些基本方法之外,还可以配合switch语句使用

    static void showEnum(SizesEnum size){
        switch (size) {
            case SMALL:
                System.out.println(size.getValue());
                break;
            case MEDIUM:
                System.out.println(size.getValue());
                break;
            case LARGE:
                System.out.println(size.getValue());
                break;
        }
    }

这里注意case中直接使用枚举元素名称,而不是SizeEnum.SMALL。

还有两个比较特殊的方法

枚举好处

安全

枚举本质也是个类,但是编译器自动做了些事情,比如上面提到values()及valueOf(String value)方法,就是通过语法糖添加的。初次之外,还有个重要的特征,就是枚举的元素是在类加载的时候,通过静态代码块来创建的。而jvm确保类初始化方法在多线程下可以被正确的加锁、同步。这样就确保了线程安全。正因为这个特性,所以可以通过枚举来实现单例。

枚举方式实现单例

直接上代码(气死我了 上才艺!!!)

public enum EnumSingleton{
    INSTANCE;

    public static EnumSingleton getInstance() {
        return EnumSingleton.INSTANCE;
    }
}

写法异常的简单。麻雀虽小,五脏俱全,下面详细说下。
我们都知道单例的原则就是只有一个实例。为了保证这个特点我们需要保证以下几方面:

  1. 在多线程模式下保证单个实例
  2. 反射生成实例的情况下是否保证单个实例
  3. 序列化反序列化情况下是否保证单个实例

我们逐一看下枚举方式实现单例是否满足。

  1. 之前上面提到了:枚举的元素是在类加载的时候,通过静态代码块来创建的。这点确保了多线程模式下只执行过一次,这是jvm保证的。
  2. 反射和序列化我们直接看例子:
    public static void main(String[] args) throws Exception {
        EnumSingleton instance = EnumSingleton.getInstance();
        //测试序列化攻击
        byte[] serialize = SerializationUtils.serialize(instance);
        EnumSingleton instance2 = SerializationUtils.deserialize(serialize);
        System.out.println(instance == instance2);

        //测试反射攻击
        Constructor<EnumSingleton> constructor = EnumSingleton.class.getConstructor();
        constructor.setAccessible(true);
        EnumSingleton instance1 = constructor.newInstance();
        System.out.println(instance == instance1);
    }

运行结果:

true
Exception in thread "main" java.lang.NoSuchMethodException: com.lv.demo.concurrency.singleton.EnumSingleton.<init>()
    at java.lang.Class.getConstructor0(Class.java:3082)
    at java.lang.Class.getConstructor(Class.java:1825)
    at com.lv.demo.concurrency.singleton.EnumTest.main(EnumTest.java:51)

通过结果第一个输出为true,说明了在序列化攻击的情况下,反序列化得到的实例和原实例是同一个。
第二个输出直接报错,这说明了jvm禁止了通过反射实例化枚举类型类。

这样就优雅的实现了单例。是不是很棒。对于单例实现分为"饿妹子"模式和“懒妹子”模式,除了枚举模式实现“懒妹子”模式单例之外,还可通过volatile+双重验证的方式实现。这里就不扩展那么多了。

但是要注意这里枚举实现单例,如果单例必须继承其他父类,那么就不要使用这种方式了。

上一篇 下一篇

猜你喜欢

热点阅读