结构
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()方法之外,还可以其他常用的方法
- toString() 等同于name()
- equals() 等同于"=="
- compareTo() 因为枚举类型实现了java API的Comparable接口,所以可以比较两个枚举,而比较的本质是比较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。
还有两个比较特殊的方法
- valueOf(String value)
- values()
这两个方法之所以特殊,是因为这是通过语法糖来为枚举添加的方法。
枚举好处
- 定义枚举的语法更为简洁
- 安全,一方面是缩小了变量取值的范围,除了定义的几种枚举元素外其他都是null。另一方面,在后面会提到。
- 方便,提供了实用的方法来对枚举进行操作。
安全
枚举本质也是个类,但是编译器自动做了些事情,比如上面提到values()及valueOf(String value)方法,就是通过语法糖添加的。初次之外,还有个重要的特征,就是枚举的元素是在类加载的时候,通过静态代码块来创建的。而jvm确保类初始化方法在多线程下可以被正确的加锁、同步。这样就确保了线程安全。正因为这个特性,所以可以通过枚举来实现单例。
枚举方式实现单例
直接上代码(气死我了 上才艺!!!)
public enum EnumSingleton{
INSTANCE;
public static EnumSingleton getInstance() {
return EnumSingleton.INSTANCE;
}
}
写法异常的简单。麻雀虽小,五脏俱全,下面详细说下。
我们都知道单例的原则就是只有一个实例。为了保证这个特点我们需要保证以下几方面:
- 在多线程模式下保证单个实例
- 反射生成实例的情况下是否保证单个实例
- 序列化反序列化情况下是否保证单个实例
我们逐一看下枚举方式实现单例是否满足。
- 之前上面提到了:枚举的元素是在类加载的时候,通过静态代码块来创建的。这点确保了多线程模式下只执行过一次,这是jvm保证的。
- 反射和序列化我们直接看例子:
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+双重验证的方式实现。这里就不扩展那么多了。
但是要注意这里枚举实现单例,如果单例必须继承其他父类,那么就不要使用这种方式了。
网友评论