版权声明
版权声明:本文为博主原创文章,转载请注明出处+地址
前言
真正想要去了解枚举是因为最近在研究单例模式,了解到枚举可以做到线程安全,反射安全,序列化安全,那么为什么枚举这么优秀呢?接下来我们一起来看下枚举的内部构造。
enum关键字到底为我们干了什么?
相信大部分的开发同学跟我一样,使用枚举就是直接创建一个enum的类,那么到底enum修饰的类做了一些什么事情呢?
首先,我定义了一个enum
public enum Color {
RED
}
然后我通过命令将其反编译
javac ******/app/src/main/java/com/dm/yzy/myapplication/Color.java
javap -c ******/app/src/main/java/com/dm/yzy/myapplication/Color.class
之后呢,生成了以下的反编译代码
public final class com.dm.yzy.myapplication.Color extends java.lang.Enum<com.dm.yzy.myapplication.Color> {
public static final com.dm.yzy.myapplication.Color RED;
public static com.dm.yzy.myapplication.Color[] values();
Code:
0: getstatic #1 // Field $VALUES:[Lcom/dm/yzy/myapplication/Color;
3: invokevirtual #2 // Method "[Lcom/dm/yzy/myapplication/Color;".clone:()Ljava/lang/Object;
6: checkcast #3 // class "[Lcom/dm/yzy/myapplication/Color;"
9: areturn
public static com.dm.yzy.myapplication.Color valueOf(java.lang.String);
Code:
0: ldc #4 // class com/dm/yzy/myapplication/Color
2: aload_0
3: invokestatic #5 // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
6: checkcast #4 // class com/dm/yzy/myapplication/Color
9: areturn
static {};
Code:
0: new #4 // class com/dm/yzy/myapplication/Color
3: dup
4: ldc #7 // String RED
6: iconst_0
7: invokespecial #8 // Method "<init>":(Ljava/lang/String;I)V
10: putstatic #9 // Field RED:Lcom/dm/yzy/myapplication/Color;
13: iconst_1
14: anewarray #4 // class com/dm/yzy/myapplication/Color
17: dup
18: iconst_0
19: getstatic #9 // Field RED:Lcom/dm/yzy/myapplication/Color;
22: aastore
23: putstatic #1 // Field $VALUES:[Lcom/dm/yzy/myapplication/Color;
26: return
}
结合以上的代码,我们可以得出以下的信息
- enum修饰的类编译之后其实是一个final类型,所以无法被继承
- enum修饰的类,其实是Enum的子类
- RED是静态且不可变的Color对象
Enum类是怎么保证线程安全的呢?
Enum源码片段:
public abstract class Enum<E extends Enum<E>>
implements Comparable<E>, Serializable {
Enum是一个抽象类,那么就无法直接在代码中进行实例化,那么就保证了无法new一个对象,那么开发者就无法在其他地方通过new的方式生成一个对象。
通过反编译之后的源码我们可以看出,RED对象是静态的,当Enum类第一次被真正使用到的时候RED对象被初始化,同时Java类的加载和初始化过程都是线程安全的。所以,这就保证了单例模式的线程安全性。
Enum类是怎么保证反射安全的呢?
Enum源码片段:
/**
* Sole constructor. Programmers cannot invoke this constructor.
* It is for use by code emitted by the compiler in response to
* enum type declarations.
*
* @param name - The name of this enum constant, which is the identifier
* used to declare it.
* @param ordinal - The ordinal of this enumeration constant (its position
* in the enum declaration, where the initial constant is assigned
* an ordinal of zero).
*/
protected Enum(String name, int ordinal) {
this.name = name;
this.ordinal = ordinal;
}
上面的解释大概意思是,唯一的构造函数,开发者不能调用这个构造函数,只提供给编译器使用,那么就阻止了开发者利用反射调用构造函数,达到了反射安全。
Enum类是怎么保证序列化安全的呢?
首先我截取了ObjectInputStream.class的readEnum的代码片段:
/**
* Reads in and returns enum constant, or null if enum type is
* unresolvable. Sets passHandle to enum constant's assigned handle.
*/
private Enum<?> readEnum(boolean unshared) throws IOException {
...
String name = readString(false);
Enum<?> result = null;
Class<?> cl = desc.forClass();
if (cl != null) {
try {
@SuppressWarnings("unchecked")
Enum<?> en = Enum.valueOf((Class)cl, name);
result = en;
} catch (IllegalArgumentException ex) {
throw (IOException) new InvalidObjectException(
"enum constant " + name + " does not exist in " +
cl).initCause(ex);
}
......
return result;
}
大家看到了吧,Enum<?> en = Enum.valueOf((Class)cl, name),这句代码很重要,我们序列化拿到的Enum对象是通过Enum.valueOf函数来获取的,那么接下来我们切换到Enum的源码,看下这个函数的实现
public static <T extends Enum<T>> T valueOf(Class<T> enumType,
String name) {
......
T[] values = getSharedConstants(enumType);
......
// Iterate backwards through the array to retain historic Android behavior in the
// unexpected / likely invalid case where there are multiple values with the same name.
for (int i = values.length - 1; i >= 0; --i) {
T value = values[i];
if (name.equals(value.name())) {
return value;
}
}
......
}
可以看出,Enum的对象是根据name在一个数组中获取的,那么这个数组是怎么来的呢?
SuppressWarnings("unchecked") // the cache always returns the type matching enumType
public static <T extends Enum<T>> T[] getSharedConstants(Class<T> enumType) {
return (T[]) sharedConstantsCache.get(enumType);
}
看的出来这是在一个cache里面去获取的,那么也就是说,Enum的对象没有被重新创建,而是直接将之前存起来的对象,通过name去获取出来,实现了序列化安全。
结束语
以上是我自己对枚举的理解,如果有纰漏,希望大家可以指正,有兴趣的同学也欢迎私信我可以一起讨论