V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
amiwrong123
V2EX  ›  Java

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

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

    最近看书刚看懂 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 s = 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   ·   我们的愿景   ·   实用小工具   ·   1413 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 27ms · UTC 17:25 · PVG 01:25 · LAX 10:25 · JFK 13:25
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.