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

2019-10-13 10:01:08 +08:00
 amiwrong123

最近看书刚看懂 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;这里三目表达式不是肯定判断为真吗

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

enum 就算实现了别的新的接口,`Class<?> zuper = clazz.getSuperclass();`getSuperclass 应该也是返回直接继承的类 Enum 啊,而不可能是接口吧==
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
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
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
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
2019-10-13 11:42:31 +08:00
推荐楼主看看小马哥的一入 java 深似海系列
amiwrong123
2019-10-13 11:48:33 +08:00
@xuanyu66
可能是因为 MyEnum$1 是匿名内部类,所以我没法看到 MyEnum$1 的类定义吧
amiwrong123
2019-10-13 11:50:34 +08:00
@wleexi
视频教程呗,哎,想看的资源都太多,都眼花缭乱了。现在只看 java 编程思想,今年能搞完这本就不错了。
amiwrong123
2019-10-13 12:00:49 +08:00
有大佬能解释一下第一个疑问吗,反正就是解释成:编译器帮我加了这两个方便的方法呗?
xuanyu66
2019-10-13 14:06:31 +08:00
@amiwrong123 不是的,也会生成 MyEnum$1.class 类的。你去本地的 targe 目录里可以看到的,ide 里面可能看不到。
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
2019-10-13 14:13:52 +08:00
你如果要在枚举常量添加方法,或者实现一个 myEnum 的抽象方法,其实本质上都是用静态内部类加继承实现的。但是其实 java 的静态内部类也是一个 trick,真正生成的时候还是会有外部类的单独文件。如果是匿名的内部类就会是$1,$2
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
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
2019-10-13 14:46:58 +08:00
@xuanyu66
谢谢回答啦,那我理解成匿名内部类(还是静态内部类)是不是理解错了=-=

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

你却说 MyEnum$1 是作为 MyEnum 的静态内部类存在的
xuanyu66
2019-10-13 15:16:33 +08:00
@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
}

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/608807

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX