最近看书刚看懂 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;
这里三目表达式不是肯定判断为真吗
1
tigerfyj 2019-10-13 10:17:03 +08:00 via Android
楼主的问题我不清楚,只提两个文中的点,也许有用。静态方法没有继承一说,所以猜是自动生成了两个静态方法。声明 enum 的时候可以实现接口,zuper 的判断可能与此有关。
|
2
amiwrong123 OP @tigerfyj
静态方法可以继承这么说可能有点不恰当,毕竟它是类相关的,而且可以被隐藏。 enum 就算实现了别的新的接口,`Class<?> zuper = clazz.getSuperclass();`getSuperclass 应该也是返回直接继承的类 Enum 啊,而不可能是接口吧== |
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()); } } ``` |
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 不会用 |
5
amiwrong123 OP @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()这里我好像还有点懵,我再看下哈== |
6
amiwrong123 OP @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== |
7
wleexi 2019-10-13 11:42:31 +08:00
推荐楼主看看小马哥的一入 java 深似海系列
|
8
amiwrong123 OP @xuanyu66
可能是因为 MyEnum$1 是匿名内部类,所以我没法看到 MyEnum$1 的类定义吧 |
9
amiwrong123 OP @wleexi
视频教程呗,哎,想看的资源都太多,都眼花缭乱了。现在只看 java 编程思想,今年能搞完这本就不错了。 |
10
amiwrong123 OP 有大佬能解释一下第一个疑问吗,反正就是解释成:编译器帮我加了这两个方便的方法呗?
|
11
xuanyu66 2019-10-13 14:06:31 +08:00
@amiwrong123 不是的,也会生成 MyEnum$1.class 类的。你去本地的 targe 目录里可以看到的,ide 里面可能看不到。
|
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 } |
13
xuanyu66 2019-10-13 14:13:52 +08:00 1
你如果要在枚举常量添加方法,或者实现一个 myEnum 的抽象方法,其实本质上都是用静态内部类加继承实现的。但是其实 java 的静态内部类也是一个 trick,真正生成的时候还是会有外部类的单独文件。如果是匿名的内部类就会是$1,$2
|
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)方法
|
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 |
16
amiwrong123 OP |
17
amiwrong123 OP @xuanyu66
懂啦,给新类新加了个静态方法,里面再去调用了父类的静态方法。 |
18
xuanyu66 2019-10-13 14:52:29 +08:00
@amiwrong123 16 楼的话没懂什么意思
|
19
amiwrong123 OP |
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 } |
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")); } } ``` 用这个对比更方便 |
22
amiwrong123 OP @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 的了。所以此时,匿名内部类不能持有外部类的引用。 |
23
xuanyu66 2019-10-13 16:01:42 +08:00
@amiwrong123 明白就 ok,我也是先跑测试了解了一下,共同学习了
|
24
xuanyu66 2019-10-13 16:02:33 +08:00
@amiwrong123 是在学 java 嘛,以后随时有问题都可以交流交流
|
25
amiwrong123 OP @xuanyu66
是呀,正在学呢。主要是看 java 编程思想这本书,不过看得仔细就读得慢了。关注你一波,以后好再 @你,哈哈哈。 |
26
xuanyu66 2019-10-13 16:18:39 +08:00
@amiwrong123 之前囫囵吞枣地看过,估计以后要重读这本书
|
27
amiwrong123 OP @xuanyu66
这本书挺好的,之前和它比还纠结 java 核心技术先看哪本,还是选了它。其实更重要的是,选了一本就好好看== |
28
xuanyu66 2019-10-13 16:38:22 +08:00
@amiwrong123 核心技术我也看过了,那本书对入门者还不错的。
|