读完这篇文章你将会收获到
- 枚举类的真正实现原理
- 为啥可以使用枚举类实现单例模式
Class
类中对枚举实例的缓存
概述
枚举是我们日常开发经常用到的一个类型 , 比如说我们有个用户系统 , 那么我们怎么判断这个是一个我们的忠实用户或者说是一个忠实粉丝呢 ? 我们就定义如下的行为枚举
public enum BehaviorEnum { /** * 关注 */ FOLLOW{ @Override void action() { System.out.println("我已经关注了 CoderLi 了"); } }, /** * 在看 */ WOW{ @Override void action() { System.out.println("CoderLi 的文章我已经点在看了"); } }, /** * 分享 */ FORWARD_TO_FRIENDS{ @Override void action() { System.out.println("CoderLi 的文章我已经分享给我的基友了"); } }, /** * 收藏 */ ADD_TO_FAVORITES{ @Override void action() { System.out.println("CoderLi 的文章已经在我的收藏里面吃灰了"); } },; abstract void action(); } 复制代码
如果说这个用户都具备上面的四种行为的话、我们就认定这个用户是一个非常棒棒棒棒棒棒棒棒的用户(看到这里的你 , 还不懂我的暗示吗 ? 原创不易 , 希望得到各位的支持)
枚举是 JDK 1.5
才开始支持的的、一开始是 Java
是不支持枚举的,就像泛型一样,本质上来说它们都是语法糖。
那它究竟是如何实现的呢 ?
分析
我们先将上面的 BehaviorEnum.java
编译成 class
文件
哦吼、居然多出那么多匿名类,我们先来看看 BehaviorEnum.class
里面有什么东东
我们看到 BehaviorEnum
变成了一个 abstract class
并且继承了 Enum
这个类,并且在这个抽象类中定义了四个 static final
类型为 BehaviorEnum
的 变量
public abstract class other.BehaviorEnum extends java.lang.Enum<other.BehaviorEnum> public static final other.BehaviorEnum FOLLOW; descriptor: Lother/BehaviorEnum; flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM public static final other.BehaviorEnum WOW; descriptor: Lother/BehaviorEnum; flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM public static final other.BehaviorEnum FORWARD_TO_FRIENDS; descriptor: Lother/BehaviorEnum; flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM public static final other.BehaviorEnum ADD_TO_FAVORITES; descriptor: Lother/BehaviorEnum; flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM 复制代码
然后我们发现了多出了一个 public
的静态方法 values
public static other.BehaviorEnum[] values(); descriptor: ()[Lother/BehaviorEnum; flags: ACC_PUBLIC, ACC_STATIC Code: stack=1, locals=0, args_size=0 0: getstatic #2 // Field $VALUES:[Lother/BehaviorEnum; 3: invokevirtual #3 // Method "[Lother/BehaviorEnum;".clone:()Ljava/lang/Object; 6: checkcast #4 // class "[Lother/BehaviorEnum;" 9: areturn 复制代码
还多出了另一个 public
的静态方法 valueOf
public static other.BehaviorEnum valueOf(java.lang.String); descriptor: (Ljava/lang/String;)Lother/BehaviorEnum; flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=1, args_size=1 0: ldc #5 // class other/BehaviorEnum 2: aload_0 3: invokestatic #6 // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum; 6: checkcast #5 // class other/BehaviorEnum 9: areturn 复制代码
一个 static
块的初始化代码块
static {}; descriptor: ()V flags: ACC_STATIC Code: stack=4, locals=0, args_size=0 0: new #8 // class other/BehaviorEnum$1 3: dup 4: ldc #9 // String FOLLOW 6: iconst_0 7: invokespecial #10 // Method other/BehaviorEnum$1."<init>":(Ljava/lang/String;I)V 10: putstatic #11 // Field FOLLOW:Lother/BehaviorEnum; 13: new #12 // class other/BehaviorEnum$2 16: dup 17: ldc #13 // String WOW 19: iconst_1 20: invokespecial #14 // Method other/BehaviorEnum$2."<init>":(Ljava/lang/String;I)V 23: putstatic #15 // Field WOW:Lother/BehaviorEnum; 26: new #16 // class other/BehaviorEnum$3 29: dup 30: ldc #17 // String FORWARD_TO_FRIENDS 32: iconst_2 33: invokespecial #18 // Method other/BehaviorEnum$3."<init>":(Ljava/lang/String;I)V 36: putstatic #19 // Field FORWARD_TO_FRIENDS:Lother/BehaviorEnum; 39: new #20 // class other/BehaviorEnum$4 42: dup 43: ldc #21 // String ADD_TO_FAVORITES 45: iconst_3 46: invokespecial #22 // Method other/BehaviorEnum$4."<init>":(Ljava/lang/String;I)V 49: putstatic #23 // Field ADD_TO_FAVORITES:Lother/BehaviorEnum; 52: iconst_4 53: anewarray #5 // class other/BehaviorEnum 56: dup 57: iconst_0 58: getstatic #11 // Field FOLLOW:Lother/BehaviorEnum; 61: aastore 62: dup 63: iconst_1 64: getstatic #15 // Field WOW:Lother/BehaviorEnum; 67: aastore 68: dup 69: iconst_2 70: getstatic #19 // Field FORWARD_TO_FRIENDS:Lother/BehaviorEnum; 73: aastore 74: dup 75: iconst_3 76: getstatic #23 // Field ADD_TO_FAVORITES:Lother/BehaviorEnum; 79: aastore 80: putstatic #2 // Field $VALUES:[Lother/BehaviorEnum; 复制代码
我们也稍微的看看 BehaviorEnum$1.class
里面存放了什么
继承自 BehaviorEnum
final class other.BehaviorEnum$1 extends other.BehaviorEnum 复制代码
action
方法则是输出对应的字符串
void action(); descriptor: ()V flags: Code: stack=2, locals=1, args_size=1 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #3 // String 我已经关注了 CoderLi 了 5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 8: return 复制代码
java.lang.Enum
我们先来认识一下这个类
public abstract class Enum<E extends Enum<E>> implements Comparable<E>, Serializable { private final String name; public final String name() { return name; } private final int ordinal; public final int ordinal() { return ordinal; } protected Enum(String name, int ordinal) { this.name = name; this.ordinal = ordinal; } public String toString() { return name; } public final boolean equals(Object other) { return this==other; } public final int hashCode() { return super.hashCode(); } protected final Object clone() throws CloneNotSupportedException { throw new CloneNotSupportedException(); } public static <T extends Enum<T>> T valueOf(Class<T> enumType, String name) { T result = enumType.enumConstantDirectory().get(name); if (result != null) return result; if (name == null) throw new NullPointerException("Name is null"); throw new IllegalArgumentException( "No enum constant " + enumType.getCanonicalName() + "." + name); } protected final void finalize() { } private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { throw new InvalidObjectException("can't deserialize enum"); } private void readObjectNoData() throws ObjectStreamException { throw new InvalidObjectException("can't deserialize enum"); } } 复制代码
在这个类中我们见到了我们常用的两个属性、一个是 name
, 一个是 ordinal
这个类也没啥特别、我们就挑几个有意思的方法看看
public final boolean equals(Object other) { return this==other; } 复制代码
枚举的 ==
和 equals
是一样的
protected final Object clone() throws CloneNotSupportedException { throw new CloneNotSupportedException(); } 复制代码
Enum
类型的对象都不支持 clone
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { throw new InvalidObjectException("can't deserialize enum"); } 复制代码
即使这里增加了这个方法、但是并不会阻碍其反序列化
所以为啥 Java
的单例模式、可以直接使用枚举来实现、现在知道原因了吧 ?
public static <T extends Enum<T>> T valueOf(Class<T> enumType, String name) { T result = enumType.enumConstantDirectory().get(name); if (result != null) return result; if (name == null) throw new NullPointerException("Name is null"); throw new IllegalArgumentException( "No enum constant " + enumType.getCanonicalName() + "." + name); } 复制代码
这里很有意思,调用 Class
对象的 enumConstantDirectory
方法
T[] getEnumConstantsShared() { if (enumConstants == null) { if (!isEnum()) return null; try { final Method values = getMethod("values"); java.security.AccessController.doPrivileged( new java.security.PrivilegedAction<Void>() { public Void run() { values.setAccessible(true); return null; } }); @SuppressWarnings("unchecked") T[] temporaryConstants = (T[])values.invoke(null); enumConstants = temporaryConstants; } // These can happen when users concoct enum-like classes // that don't comply with the enum spec. catch (InvocationTargetException | NoSuchMethodException | IllegalAccessException ex) { return null; } } return enumConstants; } private volatile transient T[] enumConstants = null; 复制代码
哦吼,有点意思、反射调用对应类型的一个 values
方法,并且只会在第一次调用的时候才会这么做。将对应的数组也保存在 enumConstants
变量中。(下面会分析到 values
这个方法的)
那我们能不能通过反射去进行实例化这个枚举类 ? 欢迎留言区侃侃你的看法
BehaviorEnum
// 初始化完 BehaviorEnum 四个静态常量之后的代码 53: anewarray #5 // class other/BehaviorEnum 56: dup 57: iconst_0 58: getstatic #11 // Field FOLLOW:Lother/BehaviorEnum; 61: aastore 62: dup 63: iconst_1 64: getstatic #15 // Field WOW:Lother/BehaviorEnum; 67: aastore 68: dup 69: iconst_2 70: getstatic #19 // Field FORWARD_TO_FRIENDS:Lother/BehaviorEnum; 73: aastore 74: dup 75: iconst_3 76: getstatic #23 // Field ADD_TO_FAVORITES:Lother/BehaviorEnum; 79: aastore 80: putstatic #2 // Field $VALUES:[Lother/BehaviorEnum; 复制代码
我们从 javap
中分析知道在 BehaviorEnum
中定义了四个类型为 BehaviorEnum
的静态常量。而这四个静态常量的初始化是在 static
代码块里面初始化的 , 分别使用 BehaviorEnum
的子类 BehaviorEnum$1 BehaviorEnum$2 BehaviorEnum$3 BehaviorEnum$4
进行实例化。那初始化完成之后这段代码还没结束啊,还有那么长、它还干了什么 ? 没错,它就是将上面的四个静态常量放在一个静态数组 VALUES
里面。不对,上面的反编译代码里根本就没有这个静态变量的声明啊 , 确实如此,因为这个 VALUES
这个变量是编译器自己合成的,我们用一个工具来看看 BehaviorEnum.class
的文件内容 (感兴趣而又不知道是啥的公众号内回复关键字:反编译,获取下载链接)
可以看到确实是有五个 fields
吧,VALUES
这个也是一个静态的私有的常量数组。static 代码块最后做的就是将四个 BehaviorEnum
的对象放入到数组中。(其实我们也能从反编译代码的 53
那里看出 anewarray
创建一个引用类型的数组 , 并且 80
那里是将其赋值给 VALUES
这个静态数组)
我们再来分析下这个静态方法 values
,这个方法挺简单的,返回一个存放 BehaviorEnum
的数组,直接获取 VALUES
这个静态数组就行了。但是你有没有发现它在返回前居然调用了 clone
的方法、为什么在返回前要调用 clone
这个方法 ? 为啥要进行浅克隆而不是深克隆 ? 欢迎在留言区写下各位的见解 ?
public static other.BehaviorEnum[] values(); descriptor: ()[Lother/BehaviorEnum; flags: ACC_PUBLIC, ACC_STATIC Code: stack=1, locals=0, args_size=0 0: getstatic #2 // Field $VALUES:[Lother/BehaviorEnum; 3: invokevirtual #3 // Method "[Lother/BehaviorEnum;".clone:()Ljava/lang/Object; 6: checkcast #4 // class "[Lother/BehaviorEnum;" 9: areturn 复制代码
最后我们来分析一下 valueOf
这个方法,这个很简单只是去调用父类的 valueOf
方法,然后做一个类型转换,变为 BehaviorEnum
对象
public static other.BehaviorEnum valueOf(java.lang.String); descriptor: (Ljava/lang/String;)Lother/BehaviorEnum; flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=1, args_size=1 0: ldc #5 // class other/BehaviorEnum 2: aload_0 3: invokestatic #6 // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum; 6: checkcast #5 // class other/BehaviorEnum 9: areturn 复制代码
至于 BehaviorEnum
的子类,基本没啥东西可以分析的、主要就是实现了方法 action
,打印了对应的语句
最后
好了,今天的文章就到这里了、留下了两个小问题欢迎大家留言区讨论
- 我们能不能通过反射去进行实例化这个枚举类 ?
- 为什么在
values
方法返回前要调用clone
这个方法 ? 为啥要进行浅克隆而不是深克隆 ?