java.lang.Enum源码getDeclaringClass()分析
文章目录
在阅读本文之前,如果读者不了解java泛型的自限定的话,可以先看一下本人博客Java泛型 自限定类型(Self-Bound Types)详解。因为java.lang.Enum
就是一个自限定的泛型类。
在分析getDeclaringClass()之前,必须讲一下枚举常量是否添加类定义{}
的差别。
枚举常量是否添加类定义{}
的差别
枚举常量未添加类定义{}
一般enum可以这么使用,这是最简单的用法了:
public enum MyEnum {
A ,
B ;
}
但有时你觉得需要加些成员变量和成员方法才足够使用:
public enum MyEnum {
A("Monday") ,//所以调用构造器时就必须得传入一个字符串
B("Tuesday");
private final String day;//自己加的成员变量,一个字符串
private MyEnum(String day) {//override掉默认构造器,以初始化成员变量
this.day = day;
}
}
day成员变量不用加final也不会报错,但作为enum的成员变量,加上final才显得正确。但这上面的两个例子都属于同一个类型——枚举常量未添加类定义{}
。
枚举常量添加了类定义{}
public enum MyEnum {
A {},
B {};
}
public enum MyEnum {
A {
void doSomething() { }
},
B {
void doSomethingElse() { }
};
}
上面两个例子都是属于同一个类型——枚举常量添加了类定义{}
,只不过第二个例子还添加了成员方法。
从字节码分析,枚举常量有无类定义{}的区别
无类定义{}
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());
}
}/*output:
class MyEnum
class MyEnum
class java.lang.Enum
class MyEnum$SS
class java.lang.Object
*/
通过javap命令,得到汇编,截取部分:
public final class MyEnum extends java.lang.Enum<MyEnum> {
public static final MyEnum A;
public static final MyEnum B;
public static MyEnum[] values();
Code:
0: getstatic #1 // Field $VALUES:[LMyEnum;
3: invokevirtual #2 // Method "[LMyEnum;".clone:()Ljava/lang/Object;
6: checkcast #3 // class "[LMyEnum;"
9: areturn
public static MyEnum valueOf(java.lang.String);
Code:
0: ldc #4 // class MyEnum
2: aload_0
3: invokestatic #5 // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
6: checkcast #4 // class MyEnum
9: areturn
static {};//静态代码块,类初始化时就会执行
Code:
0: new #4 // class MyEnum
3: dup
4: ldc #15 // String A
6: iconst_0
7: invokespecial #16 // Method "<init>":(Ljava/lang/String;I)V //调用构造器
10: putstatic #8 // Field A:LMyEnum; //初始化静态变量
13: new #4 // class MyEnum
16: dup
17: ldc #17 // String B
19: iconst_1
20: invokespecial #16 // Method "<init>":(Ljava/lang/String;I)V
23: putstatic #18 // Field B:LMyEnum;
26: iconst_2
27: anewarray #4 // class MyEnum
30: dup
31: iconst_0
32: getstatic #8 // Field A:LMyEnum;
35: aastore
36: dup
37: iconst_1
38: getstatic #18 // Field B:LMyEnum;
41: aastore
42: putstatic #1 // Field $VALUES:[LMyEnum;
45: return
}
- 可以看到,新类
MyEnum
继承了java.lang.Enum<MyEnum>
,这样就实现了自限定。 - 新类
MyEnum
的类定义有final,说明了它不能再被别人继承了。 - 在静态代码块里,可以看到调用MyEnum的构造器两次,分别用来初始化两个静态变量A和B。
有类定义{}
这个例子的枚举常量后有类定义{}
:
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());
}
}/*output:
class MyEnum
class MyEnum$1
class MyEnum
class MyEnum$SS
class java.lang.Object
*/
通过javap命令,得到汇编,截取部分:
public class MyEnum extends java.lang.Enum<MyEnum> {
public static final MyEnum A;
public static final MyEnum B;
public static MyEnum[] values();
Code:
0: getstatic #2 // Field $VALUES:[LMyEnum;
3: invokevirtual #3 // Method "[LMyEnum;".clone:()Ljava/lang/Object;
6: checkcast #4 // class "[LMyEnum;"
9: areturn
public static MyEnum valueOf(java.lang.String);
Code:
0: ldc #5 // class MyEnum
2: aload_0
3: invokestatic #6 // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
6: checkcast #5 // class MyEnum
9: areturn
MyEnum(java.lang.String, int, MyEnum$1);
Code:
0: aload_0
1: aload_1
2: iload_2
3: invokespecial #1 // Method "<init>":(Ljava/lang/String;I)V
6: return
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;
26: iconst_2
27: anewarray #5 // class MyEnum
30: dup
31: iconst_0
32: getstatic #9 // Field A:LMyEnum;
35: aastore
36: dup
37: iconst_1
38: getstatic #22 // Field B:LMyEnum;
41: aastore
42: putstatic #2 // Field $VALUES:[LMyEnum;
45: return
}
- 新类
MyEnum
的类定义相比之前没有加final了,之所以这样,是因为MyEnum
的类定义里,有匿名内部类继承了MyEnum
,所以需要去掉final。 System.out.println(MyEnum.A.getClass())
的打印结果也和上一个例子不同了,打印出来是class MyEnum$1
,很明显这是作为匿名内部类时编译器给的类名。- 再看静态代码块,
// Method MyEnum$1."<init>":(Ljava/lang/String;I)V
这句调用的匿名内部类MyEnum$1
的构造器,同理// Method MyEnum$2."<init>":(Ljava/lang/String;I)V
这句调用的匿名内部类MyEnum$2
的构造器。由于这两个匿名内部类的父类都是MyEnum,所以可以向上转型为MyEnum赋值给两个静态变量。 - 去out目录javap -c执行一下这个内部类的class文件,结果如下(win10下终端),发现
MyEnum$1
确实是继承了MyEnum
的,而且在MyEnum$1
的类定义也加了final:
C:\Users\dashen\IdeaProjects\first\out\production\first>javap -c MyEnum$1.class
Compiled from "MyEnum.java"
final class MyEnum$1 extends MyEnum {
MyEnum$1(java.lang.String, int);
Code:
0: aload_0
1: aload_1
2: iload_2
3: aconst_null
4: invokespecial #1 // Method MyEnum."<init>":(Ljava/lang/String;ILMyEnum$1;)V
7: return
}
两个例子的UML图
左边是第一个例子即没有类定义,右边是第二个例子即有类定义。
所以说这两种情况生成的enum constant是有区别的,前者类型是enum type本身,后者是enum type的内部类。
getDeclaringClass()分析
前面铺垫了这么多,终于可以开始分析getDeclaringClass()了,首先看java.lang.Enum的源码,截取部分:
//java.lang.Comparable源码
public interface Comparable<T> {
public int compareTo(T o);
}
//java.lang.Enum部分源码
public abstract class Enum<E extends Enum<E>>
implements Comparable<E>, Serializable {
//省略
public final int compareTo(E o) {
Enum<?> other = (Enum<?>)o;
Enum<E> self = this;
if (self.getClass() != other.getClass() && // optimization
self.getDeclaringClass() != other.getDeclaringClass()) // 判断是否属于同一个enum type
throw new ClassCastException();
return self.ordinal - other.ordinal; // 比较两个enum constant的序号差异
}
public final Class<E> getDeclaringClass() {
Class<?> clazz = getClass();
Class<?> zuper = clazz.getSuperclass();
return (zuper == Enum.class) ? (Class<E>)clazz : (Class<E>)zuper;
}
//省略
}
- Enum实现了Comparable接口的compareTo方法,实现逻辑很简单,首先判断自身和传进来的参数o是否属于同一个enum type,如果是,那么比较两个enum constant的序号差异。
- 但if判断里用到两个判断,一般来说,如果枚举类常量后没有类定义,那么使用
self.getClass() != other.getClass()
便足以判断是否为同一个enum type,从UML图也可以看出,枚举类常量都是同一个类型的,所以getClass()也会返回同一个Class对象。 - 但如果枚举类常量后有类定义,那么使用
self.getClass() != other.getClass()
就会错误判断了。因为此时A的类型是MyEnum$1
,B的类型是MyEnum$2
。所以需要针对这种情况,通过getDeclaringClass()再加一次判断。 - 看getDeclaringClass的内部实现,当此时是有类定义的情况时,且假设此时A调用了getDeclaringClass。那么clazz会是
MyEnum$1
的Class对象,zuper才是MyEnum
的Class对象,进入最后的三目表达式,(zuper == Enum.class)
自然判断是不相等的,所以表达式返回后者(Class<E>)zuper
(加个强转让这个引用不是带通配符的那种)。注意此时,类型参数已经被替换为具体类型MyEnum
了,所以会强转成Class<MyEnum>
。 - 所以源码里会分别判断
self.getClass() != other.getClass()
和self.getDeclaringClass() != other.getDeclaringClass()
,因为有无类定义的两种情况都需要判断到。如果这两个判断都为真,说明this和other不是同一个enum type。
public static T valueOf(String)
和public static T[] values()
观察上面两种例子生成的字节码,发现会新增加两个静态方法public static MyEnum[] values();
和public static MyEnum 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.
//只能找到注释里说了,说这两个方法是隐式声明的
//注释下面是另外一个valueOf方法
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);
}
看新增加的public static MyEnum valueOf(java.lang.String);
的汇编,里面有一句3: invokestatic #5 // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
。你终于发现,原来是MyEnum类新增的静态方法里调用Enum的静态方法。
这里还有个泛型的知识点,3: invokestatic #5 // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
这里的返回值是Enum类型,为啥不是MyEnum呢,因为类型擦除呀,所以就擦除到上界了。下一句6: checkcast #4 // class MyEnum
在调用返回处再强转成MyEnum。
友情链接
https://stackoverflow.com/questions/5758660/java-enum-getdeclaringclass-vs-getclass