乐趣区

Java-枚举

读完这篇文章你将会收获到

  • 枚举类的真正实现原理
  • 为啥可以使用枚举类实现单例模式
  • Class 类中对枚举实例的缓存

概述

枚举是我们日常开发经常用到的一个类型 , 比如说我们有个用户系统 , 那么我们怎么判断这个是一个我们的忠实用户或者说是一个忠实粉丝呢 ? 我们就定义如下的行为枚举

public enum BehaviorEnum {
    /**
     * 关注
     */
    FOLLOW{
        @Override
        void action() {System.out.println("我已经关注了 CoderLi 了");
        }
    },
    /**
     * 在看
     */
    WOW{
        @Override
        void action() {System.out.println("CoderLi 的文章我已经点在看了");
        }
    },
    /**
     * 分享
     */
    FORWARD_TO_FRIENDS{
        @Override
        void action() {System.out.println("CoderLi 的文章我已经分享给我的基友了");
        }
    },
    /**
     * 收藏
     */
    ADD_TO_FAVORITES{
        @Override
        void action() {System.out.println("CoderLi 的文章已经在我的收藏里面吃灰了");
        }
    },;

    abstract void action();}

如果说这个用户都具备上面的四种行为的话、我们就认定这个用户是一个非常棒棒棒棒棒棒棒棒的用户 (看到这里的你 , 还不懂我的暗示吗 ? 原创不易 , 希望得到各位的支持)

枚举是 JDK 1.5 才开始支持的的、一开始是 Java 是不支持枚举的,就像泛型一样,本质上来说它们都是语法糖。

那它究竟是如何实现的呢 ?

分析

我们先将上面的 BehaviorEnum.java 编译成 class 文件

哦吼、居然多出那么多匿名类,我们先来看看 BehaviorEnum.class 里面有什么东东

我们看到 BehaviorEnum 变成了一个 abstract class 并且继承了 Enum 这个类,并且在这个抽象类中定义了四个 static final 类型为 BehaviorEnum 的 变量

public abstract class other.BehaviorEnum extends java.lang.Enum<other.BehaviorEnum>
public static final other.BehaviorEnum FOLLOW;
    descriptor: Lother/BehaviorEnum;
    flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
public static final other.BehaviorEnum WOW;
    descriptor: Lother/BehaviorEnum;
    flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
public static final other.BehaviorEnum FORWARD_TO_FRIENDS;
    descriptor: Lother/BehaviorEnum;
    flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
public static final other.BehaviorEnum ADD_TO_FAVORITES;
    descriptor: Lother/BehaviorEnum;
    flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM

然后我们发现了多出了一个 public 的静态方法 values

public static other.BehaviorEnum[] values();
    descriptor: ()[Lother/BehaviorEnum;
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=1, locals=0, args_size=0
         0: getstatic     #2                  // Field $VALUES:[Lother/BehaviorEnum;
         3: invokevirtual #3                  // Method "[Lother/BehaviorEnum;".clone:()Ljava/lang/Object;
         6: checkcast     #4                  // class "[Lother/BehaviorEnum;"
         9: areturn

还多出了另一个 public 的静态方法 valueOf

  public static other.BehaviorEnum valueOf(java.lang.String);
    descriptor: (Ljava/lang/String;)Lother/BehaviorEnum;
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=1, args_size=1
         0: ldc           #5                  // class other/BehaviorEnum
         2: aload_0
         3: invokestatic  #6                  // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
         6: checkcast     #5                  // class other/BehaviorEnum
         9: areturn

一个 static 块的初始化代码块

 static {};
    descriptor: ()V
    flags: ACC_STATIC
    Code:
      stack=4, locals=0, args_size=0
         0: new           #8                  // class other/BehaviorEnum$1
         3: dup
         4: ldc           #9                  // String FOLLOW
         6: iconst_0
         7: invokespecial #10                 // Method other/BehaviorEnum$1."<init>":(Ljava/lang/String;I)V
        10: putstatic     #11                 // Field FOLLOW:Lother/BehaviorEnum;
        13: new           #12                 // class other/BehaviorEnum$2
        16: dup
        17: ldc           #13                 // String WOW
        19: iconst_1
        20: invokespecial #14                 // Method other/BehaviorEnum$2."<init>":(Ljava/lang/String;I)V
        23: putstatic     #15                 // Field WOW:Lother/BehaviorEnum;
        26: new           #16                 // class other/BehaviorEnum$3
        29: dup
        30: ldc           #17                 // String FORWARD_TO_FRIENDS
        32: iconst_2
        33: invokespecial #18                 // Method other/BehaviorEnum$3."<init>":(Ljava/lang/String;I)V
        36: putstatic     #19                 // Field FORWARD_TO_FRIENDS:Lother/BehaviorEnum;
        39: new           #20                 // class other/BehaviorEnum$4
        42: dup
        43: ldc           #21                 // String ADD_TO_FAVORITES
        45: iconst_3
        46: invokespecial #22                 // Method other/BehaviorEnum$4."<init>":(Ljava/lang/String;I)V
        49: putstatic     #23                 // Field ADD_TO_FAVORITES:Lother/BehaviorEnum;
        52: iconst_4
        53: anewarray     #5                  // class other/BehaviorEnum
        56: dup
        57: iconst_0
        58: getstatic     #11                 // Field FOLLOW:Lother/BehaviorEnum;
        61: aastore
        62: dup
        63: iconst_1
        64: getstatic     #15                 // Field WOW:Lother/BehaviorEnum;
        67: aastore
        68: dup
        69: iconst_2
        70: getstatic     #19                 // Field FORWARD_TO_FRIENDS:Lother/BehaviorEnum;
        73: aastore
        74: dup
        75: iconst_3
        76: getstatic     #23                 // Field ADD_TO_FAVORITES:Lother/BehaviorEnum;
        79: aastore
        80: putstatic     #2                  // Field $VALUES:[Lother/BehaviorEnum;

我们也稍微的看看 BehaviorEnum$1.class 里面存放了什么

继承自 BehaviorEnum

final class other.BehaviorEnum$1 extends other.BehaviorEnum

action 方法则是输出对应的字符串

void action();         
  descriptor: ()V      
  flags: 
  Code:  
    stack=2, locals=1, args_size=1   
       0: getstatic     #2    // Field java/lang/System.out:Ljava/io/PrintStream;            
       3: ldc           #3    // String 我已经关注了 CoderLi 了         
       5: invokevirtual #4    // Method java/io/PrintStream.println:(Ljava/lang/String;)V 
       8: return       

java.lang.Enum

我们先来认识一下这个类

public abstract class Enum<E extends Enum<E>>
        implements Comparable<E>, Serializable {

    private final String name;

    public final String name() {return name;}

    private final int ordinal;

    public final int ordinal() {return ordinal;}

    protected Enum(String name, int ordinal) {
        this.name = name;
        this.ordinal = ordinal;
    }

    public String toString() {return name;}

    public final boolean equals(Object other) {return this==other;}

    public final int hashCode() {return super.hashCode();
    }

    protected final Object clone() throws CloneNotSupportedException {throw new CloneNotSupportedException();
    }

    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);
    }

    protected final void finalize() {}
    
    private void readObject(ObjectInputStream in) throws IOException,
        ClassNotFoundException {throw new InvalidObjectException("can't deserialize enum");
    }

    private void readObjectNoData() throws ObjectStreamException {throw new InvalidObjectException("can't deserialize enum");
    }
}

在这个类中我们见到了我们常用的两个属性、一个是 name , 一个是 ordinal

这个类也没啥特别、我们就挑几个有意思的方法看看

public final boolean equals(Object other) {return this==other;}

枚举的 ==equals 是一样的

protected final Object clone() throws CloneNotSupportedException {throw new CloneNotSupportedException();
   }

Enum 类型的对象都不支持 clone

private void readObject(ObjectInputStream in) throws IOException,
    ClassNotFoundException {throw new InvalidObjectException("can't deserialize enum");
}

private void readObjectNoData() throws ObjectStreamException {throw new InvalidObjectException("can't deserialize enum");
}

也不支持序列化和反序列化

所以为啥 Java 的单例模式、可以直接使用枚举来实现、现在知道原因了吧 ?

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);
}

这里很有意思,调用 Class 对象的 enumConstantDirectory 方法

T[] getEnumConstantsShared() {if (enumConstants == null) {if (!isEnum()) return null;
        try {final Method values = getMethod("values");
            java.security.AccessController.doPrivileged(new java.security.PrivilegedAction<Void>() {public Void run() {values.setAccessible(true);
                            return null;
                        }
                    });
            @SuppressWarnings("unchecked")
            T[] temporaryConstants = (T[])values.invoke(null);
            enumConstants = temporaryConstants;
        }
        // These can happen when users concoct enum-like classes
        // that don't comply with the enum spec.
        catch (InvocationTargetException | NoSuchMethodException |
               IllegalAccessException ex) {return null;}
    }
    return enumConstants;
}
private volatile transient T[] enumConstants = null;

哦吼,有点意思、反射调用对应类型的一个 values 方法,并且只会在第一次调用的时候才会这么做。将对应的数组也保存在 enumConstants 变量中。(下面会分析到 values 这个方法的 )

那我们能不能通过反射去进行实例化这个枚举类 ? 欢迎留言区侃侃你的看法

BehaviorEnum

// 初始化完 BehaviorEnum 四个静态常量之后的代码
53: anewarray     #5                  // class other/BehaviorEnum
56: dup
57: iconst_0
58: getstatic     #11                 // Field FOLLOW:Lother/BehaviorEnum;
61: aastore
62: dup
63: iconst_1
64: getstatic     #15                 // Field WOW:Lother/BehaviorEnum;
67: aastore
68: dup
69: iconst_2
70: getstatic     #19                 // Field FORWARD_TO_FRIENDS:Lother/BehaviorEnum;
73: aastore
74: dup
75: iconst_3
76: getstatic     #23                 // Field ADD_TO_FAVORITES:Lother/BehaviorEnum;
79: aastore
80: putstatic     #2                  // Field $VALUES:[Lother/BehaviorEnum;

我们从 javap 中分析知道在 BehaviorEnum 中定义了四个类型为 BehaviorEnum 的静态常量。而这四个静态常量的初始化是在 static 代码块里面初始化的 , 分别使用 BehaviorEnum 的子类 BehaviorEnum$1 BehaviorEnum$2 BehaviorEnum$3 BehaviorEnum$4 进行实例化。那初始化完成之后这段代码还没结束啊,还有那么长、它还干了什么 ? 没错,它就是将上面的四个静态常量放在一个静态数组 VALUES 里面。不对,上面的反编译代码里根本就没有这个静态变量的声明啊 , 确实如此,因为这个 VALUES 这个变量是编译器自己合成的,我们用一个工具来看看 BehaviorEnum.class 的文件内容 (感兴趣而又不知道是啥的公众号内回复关键字: 反编译 , 获取下载链接 )

可以看到确实是有五个 fields 吧,VALUES 这个也是一个静态的私有的常量数组。static 代码块最后做的就是将四个 BehaviorEnum 的对象放入到数组中。(其实我们也能从反编译代码的 53 那里看出 anewarray 创建一个引用类型的数组 , 并且 80 那里是将其赋值给 VALUES 这个静态数组 )

我们再来分析下这个静态方法 values,这个方法挺简单的,返回一个存放 BehaviorEnum 的数组,直接获取 VALUES 这个静态数组就行了。但是你有没有发现它在返回前居然调用了 clone 的方法、为什么在返回前要调用 clone 这个方法 ? 为啥要进行浅克隆而不是深克隆 ? 欢迎在留言区写下各位的见解 ?

public static other.BehaviorEnum[] values();
    descriptor: ()[Lother/BehaviorEnum;
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=1, locals=0, args_size=0
         0: getstatic     #2                  // Field $VALUES:[Lother/BehaviorEnum;
         3: invokevirtual #3                  // Method "[Lother/BehaviorEnum;".clone:()Ljava/lang/Object;
         6: checkcast     #4                  // class "[Lother/BehaviorEnum;"
         9: areturn

最后我们来分析一下 valueOf 这个方法,这个很简单只是去调用父类的 valueOf 方法,然后做一个类型转换,变为 BehaviorEnum 对象

  public static other.BehaviorEnum valueOf(java.lang.String);
    descriptor: (Ljava/lang/String;)Lother/BehaviorEnum;
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=1, args_size=1
         0: ldc           #5                  // class other/BehaviorEnum
         2: aload_0
         3: invokestatic  #6                  // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
         6: checkcast     #5                  // class other/BehaviorEnum
         9: areturn

至于 BehaviorEnum 的子类,基本没啥东西可以分析的、主要就是实现了方法 action , 打印了对应的语句

最后

好了,今天的文章就到这里了、留下了两个小问题欢迎大家留言区讨论

  • 我们能不能通过反射去进行实例化这个枚举类 ?
  • 为什么在 values 方法返回前要调用 clone 这个方法 ? 为啥要进行浅克隆而不是深克隆 ?

相关文章

  • 我的工具箱

退出移动版