[不懂就问] Java .lang.Enum 源码的两个疑问 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
amiwrong123
V2EX    Java

[不懂就问] Java .lang.Enum 源码的两个疑问

  •  
  •   amiwrong123 2019-10-13 10:01:08 +08:00 4456 次点击
    这是一个创建于 2192 天前的主题,其中的信息可能已经有所发展或是发生改变。

    最近看书刚看懂 java 泛型的自限定,合计去找找源码的应用,发现 enum 这样用的。 下面就是一个枚举类的使用:

    public enum WeekDay { Mon("Monday"), Tue("Tuesday"), Wed("Wednesday"), Thu("Thursday"), Fri( "Friday"), Sat(Saturday"), Sun("Sunday"); private final String day; private WeekDay(String day) { this.day = day; } public static void printDay(int i){ switch(i){ case 1: System.out.println(WeekDay.Mon); break; case 2: System.out.println(WeekDay.Tue);break; case 3: System.out.println(WeekDay.Wed);break; case 4: System.out.println(WeekDay.Thu);break; case 5: System.out.println(WeekDay.Fri);break; case 6: System.out.println(WeekDay.Sat);break; case 7: System.out.println(WeekDay.Sun);break; default:System.out.println("wrong number!"); } } public String getDay() { return day; } public static void main(String[] args) { WeekDay a = WeekDay.Mon; } } 

    通过 javap 命令才能看出来新类 WeekDay 实际继承了 java.lang.Enum,public abstract class Enum<E extends Enum<E>> implements Comparable<E>, Serializable { }。 截取部分汇编来看,发现确实继承了 java.lang.Enum,看它的成员和方法的类型,也确实做到了自限定:

    public final class WeekDay extends java.lang.Enum<WeekDay> { public static final WeekDay Mon; public static final WeekDay Tue; public static final WeekDay Wed; public static final WeekDay Thu; public static final WeekDay Fri; public static final WeekDay Sat; public static final WeekDay Sun; public static WeekDay[] values(); public static WeekDay valueOf(java.lang.String); 

    于是看了看 Enum 的源码,有了几个疑问: 1.从汇编看来,好像继承来了两个方法,public static WeekDay[] values();public static WeekDay valueOf(java.lang.String);,但是在源码里找不到这两个静态方法的定义。只能在注释里找到:

     * <p>Note that for a particular enum type {@code T}, the * implicitly declared {@code public static T valueOf(String)} * method on that enum may be used instead of this method to map * from a name to the corresponding enum constant. All the * constants of an enum type can be obtained by calling the * implicit {@code public static T[] values()} method of that * type. //只能找到注释里说了,说这两个方法是隐式声明的,什么鬼? //注释下面是这个方法 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); } 

    2.getDeclaringClass 方法为啥这么实现?

     public final int compareTo(E o) { Enum<?> other = (Enum<?>)o; Enum<E> self = this; if (self.getClass() != other.getClass() && // optimization self.getDeclaringClass() != other.getDeclaringClass()) throw new ClassCastException(); return self.ordinal - other.ordinal; } @SuppressWarnings("unchecked") public final Class<E> getDeclaringClass() { Class<?> clazz = getClass(); Class<?> zuper = clazz.getSuperclass(); return (zuper == Enum.class) ? (Class<E>)clazz : (Class<E>)zuper; } 

    compareTo 是 Comparable 接口里的方法,这里 Enum 源码帮忙实现了。compareTo 的实现比较清晰,首先看是不是同一种 enum type,如果是,再比较两个 enum constant。但是用到了 getDeclaringClass 方法,这个方法有点奇怪哎,首先我觉得 self.getClass() != other.getClass()这样就足够判断是不是同一种 enum type 了呀?

    然后,再看 getDeclaringClass 方法的逻辑,Class<?> clazz = getClass();调用自己的成员方法获得自己的 Class 对象,然后Class<?> zuper = clazz.getSuperclass();获得自己父类的 Class 对象,自己的父类不是肯定是 Enum 吗?那最后return (zuper == Enum.class) ? (Class<E>)clazz : (Class<E>)zuper;这里三目表达式不是肯定判断为真吗

    28 条回复    2019-10-13 16:38:22 +08:00
    tigerfyj
        1
    tigerfyj  
       2019-10-13 10:17:03 +08:00 via Android
    楼主的问题我不清楚,只提两个文中的点,也许有用。静态方法没有继承一说,所以猜是自动生成了两个静态方法。声明 enum 的时候可以实现接口,zuper 的判断可能与此有关。
    amiwrong123
        2
    amiwrong123  
    OP
       2019-10-13 10:33:41 +08:00
    @tigerfyj
    静态方法可以继承这么说可能有点不恰当,毕竟它是类相关的,而且可以被隐藏。

    enum 就算实现了别的新的接口,`Class<?> zuper = clazz.getSuperclass();`getSuperclass 应该也是返回直接继承的类 Enum 啊,而不可能是接口吧==
    xuanyu66
        3
    xuanyu66  
       2019-10-13 11:01:30 +08:00
    ```
    public enum MyEnum {

    A ,

    B ;

    static class SS {

    }
    public static void main(String[] args) {
    System.out.println(MyEnum.A.getDeclaringClass());
    System.out.println(MyEnum.A.getClass());
    System.out.println(MyEnum.A.getClass().getSuperclass());
    SS = new SS();
    System.out.println(s.getClass());
    System.out.println(s.getClass().getSuperclass());
    }
    }
    ```
    ```
    public enum MyEnum {

    A {
    void doSomething() { }
    },


    B {
    void doSomethingElse() { }
    };

    static class SS {

    }
    public static void main(String[] args) {
    System.out.println(MyEnum.A.getDeclaringClass());
    System.out.println(MyEnum.A.getClass());
    System.out.println(MyEnum.A.getClass().getSuperclass());
    SS s = new SS();
    System.out.println(s.getClass());
    System.out.println(s.getClass().getSuperclass());
    }
    }

    ```
    xuanyu66
        4
    xuanyu66  
       2019-10-13 11:08:28 +08:00
    楼主可以试一下代码,如果在枚举常量里添加了方法的话,应该是会生成一个静态内部类继承你的枚举类。这样子的话调用 getclass 没法判断类型是否一致。
    https://stackoverflow.com/questions/5758660/java-enum-getdeclaringclass-vs-getclass

    https://blog.csdn.net/mhmyqn/article/details/48087247

    v2ex 的 markdown 不会用
    amiwrong123
        5
    amiwrong123  
    OP
       2019-10-13 11:11:19 +08:00
    @xuanyu66
    这位大哥,我好像懂你意思, 你第二个例子,运行结果居然是:
    class MyEnum
    class MyEnum$1
    class MyEnum
    class MyEnum$SS
    class java.lang.Object

    合着第二个例子里面的 A 和 B 都是内部类了呗,所以 MyEnum.A.getClass()打印出来是 class MyEnum$1 内部类的样子。

    而 MyEnum.A.getDeclaringClass()这里我好像还有点懵,我再看下哈==
    amiwrong123
        6
    amiwrong123  
    OP
       2019-10-13 11:27:14 +08:00
    @xuanyu66
    大概懂了,只是有点气,不管怎么看,都看不到内部类 A 继承了 MyEnum,这是 javap -c 后看见的:
    ```asm
    static {};
    Code:
    0: new #16 // class MyEnum$1
    3: dup
    4: ldc #17 // String A
    6: iconst_0
    7: invokespecial #18 // Method MyEnum$1."<init>":(Ljava/lang/String;I)V
    10: putstatic #9 // Field A:LMyEnum;
    13: new #19 // class MyEnum$2
    16: dup
    17: ldc #20 // String B
    19: iconst_1
    20: invokespecial #21 // Method MyEnum$2."<init>":(Ljava/lang/String;I)V
    23: putstatic #22 // Field B:LMyEnum;
    ```
    只能勉强看到静态代码块里面,分别初始化了 MyEnum$1 和 MyEnum$2 给自己的静态变量。但就是看不到内部类 A 继承了 MyEnum==
    wleexi
        7
    wleexi  
       2019-10-13 11:42:31 +08:00
    推荐楼主看看小马哥的一入 java 深似海系列
    amiwrong123
        8
    amiwrong123  
    OP
       2019-10-13 11:48:33 +08:00
    @xuanyu66
    可能是因为 MyEnum$1 是匿名内部类,所以我没法看到 MyEnum$1 的类定义吧
    amiwrong123
        9
    amiwrong123  
    OP
       2019-10-13 11:50:34 +08:00
    @wleexi
    视频教程呗,哎,想看的资源都太多,都眼花缭乱了。现在只看 java 编程思想,今年能搞完这本就不错了。
    amiwrong123
        10
    amiwrong123  
    OP
       2019-10-13 12:00:49 +08:00
    有大佬能解释一下第一个疑问吗,反正就是解释成:编译器帮我加了这两个方便的方法呗?
    xuanyu66
        11
    xuanyu66  
       2019-10-13 14:06:31 +08:00
    @amiwrong123 不是的,也会生成 MyEnum$1.class 类的。你去本地的 targe 目录里可以看到的,ide 里面可能看不到。
    xuanyu66
        12
    xuanyu66  
       2019-10-13 14:10:18 +08:00
    λ javap -c MyEnum$1.class
    Compiled from "MyEnum.java"
    final class org.bupt.pms.consistence.MyEnum$1 extends org.bupt.pms.consistence.MyEnum {
    org.bupt.pms.consistence.MyEnum$1(java.lang.String, int);
    Code:
    0: aload_0
    1: aload_1
    2: iload_2
    3: aconst_null
    4: invokespecial #1 // Method org/bupt/pms/consistence/MyEnum."<init>":(Ljava/lang/String;ILorg/bupt/pms/consistence/MyEnum$1;)V
    7: return

    void doSomething();
    Code:
    0: return
    }
    xuanyu66
        13
    xuanyu66  
       2019-10-13 14:13:52 +08:00   1
    你如果要在枚举常量添加方法,或者实现一个 myEnum 的抽象方法,其实本质上都是用静态内部类加继承实现的。但是其实 java 的静态内部类也是一个 trick,真正生成的时候还是会有外部类的单独文件。如果是匿名的内部类就会是$1,$2
    xuanyu66
        14
    xuanyu66  
       2019-10-13 14:28:11 +08:00
    @amiwrong123 对于第一个问题,就是在生成 MyEnum 的时候会给你生成一个 public static T valueOf ( String )的方法,他其实是在内部调用了 Enum 的 public static <T extends Enum<T>> T valueOf(Class<T> enumType,String name)方法
    xuanyu66
        15
    xuanyu66  
       2019-10-13 14:30:36 +08:00
    public static org.bupt.pms.consistence.MyEnum valueOf(java.lang.String);
    Code:
    0:ldc #5 // class org/bupt/pms/consistence/My Enum
    2: aload_0
    3: invokestatic #6 // Method java/lang/Enum.valueOf:(Lj ava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
    6: checkcast #5 // class org/bupt/pms/consistence/My Enum
    9: areturn
    amiwrong123
        16
    amiwrong123  
    OP
       2019-10-13 14:46:58 +08:00
    @xuanyu66
    谢谢回答啦,那我理解成匿名内部类(还是静态内部类)是不是理解错了=-=

    虽说,static context 下的匿名内部类和静态内部类是一样的。
    amiwrong123
        17
    amiwrong123  
    OP
       2019-10-13 14:48:26 +08:00
    @xuanyu66
    懂啦,给新类新加了个静态方法,里面再去调用了父类的静态方法。
    xuanyu66
        18
    xuanyu66  
       2019-10-13 14:52:29 +08:00
    @amiwrong123 16 楼的话没懂什么意思
    amiwrong123
        19
    amiwrong123  
    OP
       2019-10-13 15:00:34 +08:00
    @xuanyu66
    就是我以为 MyEnum$1 是作为 MyEnum 的匿名内部类存在的(因为它的名字长得就像)

    你却说 MyEnum$1 是作为 MyEnum 的静态内部类存在的
    xuanyu66
        20
    xuanyu66  
       2019-10-13 15:16:33 +08:00   1
    @amiwrong123 别纠结这些定义吧。其实静态内部类也可以是没名字的啊。
    ```
    public enum MyEnum {

    A {
    void doSomething() { }
    },


    B {
    void doSomethingElse() { }
    };

    static class SS {

    }

    public void countDown(){
    new Thread(){
    @Override
    public void run() {

    }
    }.start();
    }

    public static void main(String[] args) {
    System.out.println(MyEnum.A.getDeclaringClass());
    System.out.println(MyEnum.A.getClass());
    System.out.println(MyEnum.A.getClass().getSuperclass());
    SS s = new SS();
    System.out.println(s.getClass());
    System.out.println(s.getClass().getSuperclass());
    System.out.println(Enum.valueOf(MyEnum.class,"A"));
    }
    }
    ```
    你看这段代码运行后会生成 MyEnum$3.class,就是你指的所谓的”匿名内部类“。你会发现底层实现不区分这些区别。没有指定名字的类就是从 1 开始编排,如果你是 static 就不会传外部类的引用,不是 static 就传引用。
    class org.bupt.pms.consistence.MyEnum$3 extends java.lang.Thread {
    final org.bupt.pms.consistence.MyEnum this$0;

    org.bupt.pms.consistence.MyEnum$3(org.bupt.pms.consistence.MyEnum); //看这里
    Code:
    0: aload_0
    1: aload_1
    2: putfield #1 // Field this$0:Lorg/bupt/pms/consistence/MyEnum;
    5: aload_0
    6: invokespecial 2 // Method java/lang/Thread."<init>":()V
    9: return

    public void run();
    Code:
    0: return
    }
    xuanyu66
        21
    xuanyu66  
       2019-10-13 15:20:54 +08:00
    我其实也不是很懂为啥把”匿名内部类“规定为非静态内部类。静态内部类就不能匿名了吗
    ```
    public enum MyEnum {

    A {
    void doSomething() { }
    },


    B {
    void doSomethingElse() { }
    };

    static class SS {

    }

    public static void countDown(){
    new Thread(){
    @Override
    public void run() {

    }
    }.start();
    }

    public void countDown1(){
    new Thread(){
    @Override
    public void run() {

    }
    }.start();
    }

    public static void main(String[] args) {
    System.out.println(MyEnum.A.getDeclaringClass());
    System.out.println(MyEnum.A.getClass());
    System.out.println(MyEnum.A.getClass().getSuperclass());
    SS s = new SS();
    System.out.println(s.getClass());
    System.out.println(s.getClass().getSuperclass());
    System.out.println(Enum.valueOf(MyEnum.class,"A"));
    }
    }
    ```
    用这个对比更方便
    amiwrong123
        22
    amiwrong123  
    OP
       2019-10-13 15:36:03 +08:00
    @xuanyu66 #20
    你这个例子我懂啦,其实你只是想强调 内部类有没有外部类对象的引用,这个意思嘛。
    而 MyEnum$1 是没有持有的。

    @xuanyu66 #21
    这个我说一下吧,匿名内部类要分情况的:
    你 20 楼的说这个例子,就是 new Thread(){},因为它处于 non-static cnotext 这样的上下文里( countDown 是个成员方法嘛,所以就是非静态的上下文),所以这时匿名内部类持有了外部类的引用。

    然后你最开始给我说的例子:
    public enum MyEnum {
    ```
    A {
    void doSomething() { }
    },


    B {
    void doSomethingElse() { }
    };
    ```
    其实我认为它在实现上相当于:
    ```
    public static final MyEnum A = new MyEnum{
    void doSomething() { }
    }
    ```
    但偏偏这个匿名内部类赋值给了一个静态变量,那么它便是 static cnotext 的了。所以此时,匿名内部类不能持有外部类的引用。
    xuanyu66
        23
    xuanyu66  
       2019-10-13 16:01:42 +08:00
    @amiwrong123 明白就 ok,我也是先跑测试了解了一下,共同学习了
    xuanyu66
        24
    xuanyu66  
       2019-10-13 16:02:33 +08:00
    @amiwrong123 是在学 java 嘛,以后随时有问题都可以交流交流
    amiwrong123
        25
    amiwrong123  
    OP
       2019-10-13 16:11:13 +08:00
    @xuanyu66
    是呀,正在学呢。主要是看 java 编程思想这本书,不过看得仔细就读得慢了。关注你一波,以后好再 @你,哈哈哈。
    xuanyu66
        26
    xuanyu66  
       2019-10-13 16:18:39 +08:00
    @amiwrong123 之前囫囵吞枣地看过,估计以后要重读这本书
    amiwrong123
        27
    amiwrong123  
    OP
       2019-10-13 16:32:54 +08:00
    @xuanyu66
    这本书挺好的,之前和它比还纠结 java 核心技术先看哪本,还是选了它。其实更重要的是,选了一本就好好看==
    xuanyu66
        28
    xuanyu66  
       2019-10-13 16:38:22 +08:00
    @amiwrong123 核心技术我也看过了,那本书对入门者还不错的。
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     3562 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 28ms UTC 00:39 PVG 08:39 LAX 17:39 JFK 20:39
    Do have faith in what you're doing.
    ubao snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86