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

posted @ 2019-10-13 15:39  allMayMight  阅读(342)  评论(0编辑  收藏  举报