深入理解Java枚举
2021-02-03 04:18
标签:exception 意思 ESS 实践 加载 pes java源代码 rar 入栈 老实说,挺羞愧的,这么久了,一直不知道Java枚举的本质是啥,虽然也在用,但是真不知道它的底层是个啥样的 直到2020年4月28日的晚上20点左右,我才真的揭开了Java枚举的面纱,看到了它的真面目,但是我哭了 这篇文章不是深入理解枚举,而是认识枚举,是的,因为之前都是陌路人 在几个月以前,遇到需要自定义一个 创建好父类,让小崽子们来认父? 然而,我以为小崽子没有爸爸的,谁知道编译器告诉我,它已经有了爸爸!!! 那就是 当时也没在意,有就有了,有了还得我麻烦了。 前两天群里有个人问,说重写了枚举类的 先是怀疑他哪里没搞对,不可能重写 我的第一动作是进行自洽解释,从结果去推导原因 这是大忌,代码的事情,就让代码来说 给出了一个十分可笑的解释 枚举类里的枚举常量是继承自java.lang.Enum,而你重写的是枚举类的toString(),是java.lang.Object的toString()被重写了,所以不起作用 还别说,我当时还挺高兴的,发现一个 不过虽然想把上面的 因为当时讨论的时候,我好像提到过 这一看不当紧,才知道当时说那话是多么的可笑 废话不多说,上涩图 上图是枚举类Java源代码 下图是上图编译后的Class文件反编译后的 javap -c classFilePath 反编译后的内容可能很多人都看不懂,我也不咋懂,不过我们主要看前面几行就差不多了。 第一行就是表明父子关系的类继承,这里就证实,编译器做了手脚的,强行给 下面几行就有意思了 然后就很容易想到这个 是多么多么多么的相似! 可以看到,我们在Java源码中写的 编译器帮我们做了这个操作 也就是说我们所写的枚举类,其实可以这么来写,效果等同 这个普通的的Java类,和我们上面写的 它们真的一样啊,哇槽! 这个同时也解释了我的一个疑问 为啥我枚举类型,如果想表示别的信息数据时,一定要有相应的成员变量,以及一个对应的构造器? 这个构造器谁来调用呢? 它来调用,这个静态块的内容实际上就是 Tps: 之前分不清类初始化构造器,和实例初始化构造器,可以这么理解 这就是为啥需要对应的有参构造器的原因 到这里还是存有一些疑问 我们定义了一个枚举类,肯定是需要拿来使用的,尤其是当我们的枚举类还有一些其他有意义的字段的时候 比如我们上面的例子 方式也很简单 通过上面就可以找到枚举值 可是找遍了我们自己写的枚举类 如果你细心的看到这里,应该是能明白的 我们上面通过分析反编译后的字节码,看到两处可疑目标 下面这段在开始的截图有出现 上面之所以要使用 后面一处,就是在 从这两处反编译后的字节码,我们能很清晰明了的知道这个套路了 编译器自己给我们强行插入一个静态方法 到这里还没完,我们再来看个有意思的 我们的 也就是我们只能通过变量名 以前因为枚举用的少,也就仅仅停留在使用的层面,其实在使用的过程中,也有很多疑惑产生,但是并没有真正像现在这样去深究它的实现。 也许是之前动力不足,也许是对未知的恐惧,也许是其他方面的知识准备还不够。 总之,到现在才算真的理解Java枚举 关于 我也不知道这种方式对不对,对我来说,我是这样做的,其实不利于快速吸收知识,但是长久下来,会让自己的广度拓展开来,并且遇到一些新的知识点的时候,可以更容易理解它。 拿这次决定看反编译的字节码这个事,如果放在一个月前,我是不敢的,真的不敢,看不懂,头大,不会有这个想法的。 前段时间想把Java的动态代理搞一搞,很多框架都用了动态代理,不整明白,看源码很糊涂。 因此决定看看,然后找到了梁飞关于在设计 后来决定,了解下字节码吧,把 初看虽然很多,但是共性很大,实际的那些操作码并不是很多,多记几遍就可以了 我喜欢这种明了的感觉,虽然快感后是 一览无余的感觉真爽! 深入理解Java枚举 标签:exception 意思 ESS 实践 加载 pes java源代码 rar 入栈 原文地址:https://www.cnblogs.com/heartlake/p/12805535.html深入理解Java枚举
重新认识Java枚举缘起
mybatis
枚举类型的TypeHandler
,当时有多个枚举类型,想写一个Handler
搞定的,实践中发现,这些枚举类型得有一个共同的父类,才能实现,缺父类?没问题,给它们安排上!java.lang.Enum
这个类,它是一个抽象类,其Java Doc明确写到This is the common base class of all Java language enumeration types.
toString
方法,怎么没有生效呢?toString
不起作用的。
知识盲点
,打算写下来,现在想来,那不是盲点,是瞎了知识盲点
写下来,但是还是有些好奇,想弄明白怎么回事java.lang.Enum
是Java中所有枚举类的父类,当时说到了是在编译器,给它整个爸爸的,所以想看看一个枚举类编译后是什么样的。顿悟
enum
修饰的的类安排了一个爸爸 public static final com.example.demo.enu.DemoEnum ONE;
public static final com.example.demo.enu.DemoEnum TWO;
public static final com.example.demo.enu.DemoEnum THREE;
int num;
ONE(1),
TWO(2),
THREE(3);
int num;
ONE(1)
在编译后的实际上是一个DemoEnum
类型的常量ONE == public static final com.example.demo.enu.DemoEnum ONE
public class EqualEnum {
public static final EqualEnum ONE = new EqualEnum(1);
public static final EqualEnum TWO = new EqualEnum(2);
public static final EqualEnum THREE = new EqualEnum(3);
int num ;
public EqualEnum (int num) {
this.num = num;
}
}
public enum DemoEnum {
ONE(1),
TWO(2),
THREE(3);
int num;
DemoEnum (int num) {
this.num = num;
}
}
构造器的内容
static {};
Code:
// 创建一个DemoEnum对象
0: new #4 // class com/example/demo/enu/DemoEnum
// 操作数栈顶复制并且入栈
3: dup
// 把String ONE 入栈
4: ldc #14 // String ONE
// int常量值0入栈
6: iconst_0
7: iconst_1
// 调用实例初始化方法
8: invokespecial #15 // Method "
ONE(1)
,通过1
这个数值,去获得枚举值 ONE
,这是很常见的一个需求。DemoEnum[] vals = DemoEnum.values()
for(int i=0; i
ONE
DemoEnum
和它的强行安排的父类Enum
,都没有找到静态方法values
public static com.example.demo.enu.DemoEnum[] values();
Code:
// 获取静态域 $VALUES的值
0: getstatic #1 // Field $VALUES:[Lcom/example/demo/enu/DemoEnum;
// 调用clone()方法
3: invokevirtual #2 // Method "[Lcom/example/demo/enu/DemoEnum;".clone:()Ljava/lang/Object;
// 类型检查
6: checkcast #3 // class "[Lcom/example/demo/enu/DemoEnum;"
// 返回clone()后的方法
9: areturn
clone()
,是避免调用values()
,将内部的数组暴露出去,从而有被修改的分险,也存在线程安全问题static{}
块最后那部分values()
,而且还有一个 T[] $VALUES
数组,不过这个静态域在源码没找到,估计是编译器编译时加进去的java.lang.Class#getEnumConstantsShared
,在java.lang.Class
中有这么个方法,访问修饰符是default
,包访问级别的T[] getEnumConstantsShared() {
if (enumConstants == null) {
if (!isEnum()) return null;
try {
// 看这里 看这里 看这里
final Method values = getMethod("values");
java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction
valuesOf
方法,在底层就是调用它来实现的,很遗憾的是,这个valuesOf
方法,仅仅实现了通过枚举类型的name
来查找对应的枚举值。 name = "ONE"
这种方式,来查找到DemoEnum.ONE
这个枚举值后记
其他方面的知识准备不足
,这个我觉得还是值得说一下的,之前我就写过一次说这个事的,因为有些知识点,它并不是孤立的,是网状的,我们在看某一个点的时候,往往就像在一个蜘蛛网上,但是这个网上太多我们不知道的东西了,所以就很容易出现去不断的补充和它相关的知识点的情况,这个时候就会很累,而且,你最开始想学的那个知识点,也没怎么搞懂。Dubbo
时对动态代理的选择的一篇文章,里面贴出了几种动态代理生成的字节码的对比,看不到懂,满脑子问号。《深入理解Java虚拟机》
这本书翻出来,翻到最后的附录部分,看了一遍索然无味
,不过这也能正向激励去不断的探索未知,而不是因为恐惧而退却!