最近突然有人问了个关于JAVA枚举类的问题,问题的大概意思是:通过IDE反编译查看的构造函数和通过编程方式反射得到的构造函数不一样。这个问题以前没见过,因此花费了些力气去学习了解了下,弄懂之后在此做个记录。
问题引出
一个简单的枚举类
1 | public enum Question { |
编译出来的字节码文件在IDE中的显示是这样的
1 | public enum Question { |
但通过编程反射后得到的结果是这样的private com.hst.Question(java.lang.String,int)
1 |
|
那么问题来了:实际使用的构造函数究竟是哪个,是IDE显示的无参构造还是反射得到的有参构造?
问题分析
在之前的博客中已经提到过,由于JVM运行的是字节码文件而非JAVA源码,所以这种问题需要从字节码入手,使用javap分析字节码得到如下内容
1 | public final class com.hst.Question extends java.lang.Enum<com.hst.Question> |
在第一行可以看到,编写的Question枚举类实际上是继承了java.lang.Enum
类型,而定义的INSTANCE
实际为Question中的一个静态类变量。接着可以看到枚举类还有一段static {}
静态初始化块的指令,这个在之前JAVA字段初始化的文章中有提到过,静态初始化块代码是在类加载期执行,类加载之后进行实例化则不会再执行。这段静态初始化的指令整体上实际分为两个部分,这里详细介绍下这段指令
- 0创建类Question的实例
- 3复制实例引用至操作栈
- 4加载常量池字符串INSTANCE至操作栈
- 6加载int常量0至操作栈
- 7调用构造方法(Ljava/lang/String;I)V
- 10将实例设置为Question静态类变量INSTANCE
- 13加载常量1
- 14创建长度为1的Question数组实例
- 17复制数组实例引用至操作栈
- 18加载常量0至操作栈
- 19获取静态类变量INSTANCE引用至操作栈
- 22将Question数组0的位置存储静态类变量INSTANCE引用
- 23将Question数组实例设置为Question的$VALUES静态类变量
- 26返回
如果要用实际代码来演示的话,真实的枚举类源码应该是这样的
1 | public final class Question extends Enum<Question> { |
所以综上所述,IDE实际是优化了显示,而实际构造函数则是来源于java.lang.Enum
中的protected Enum(String name, int ordinal)
,即使枚举类有自己的字段和构造函数,但实际的字节码中依然会加上默认的参数(String name, int ordinal)
,另外关于枚举类的$VALUES字段有兴趣的同学可以自己通过反射获取验证下。
写在最后
果然问题还是学习的第一驱动力,枚举类之前用的倒是挺多的,没想到里面还有这么多名堂,嗯,感谢提出问题的大哥。希望这篇文章的内容可以帮助大家。