读完这篇文章你将会收获到
- 枚举类的真正实现原理
- 为啥可以使用枚举类实现单例模式
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
这个方法 ? 为啥要进行浅克隆而不是深克隆 ?
相关文章
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?