java Enum

在项目中经常使用Enum,我们知道Enum类型只能使用已经定义好的Enum类型,不能对Enum进行实例化。但是从来没有考虑过java是如何实现这个类型的。今天同事问了这个问题,借此机会正好看看。

首先定义Enum类型,

public enum EnumTest {
    enum1(1, 1), enum2(2, 2);
    private int i;
    private int j;
    EnumTest(int i, int j) {
        this.i = i;
        this.j = j;
    }
}

使用javap查看生成的class文件,

javap -verbose EnumTest.class

生成的内容如下所示,

Classfile /C:/Users/Administrator/workspace/Enum/bin/EnumTest.class
  Last modified 2015-7-29; size 1015 bytes
  MD5 checksum 17047593684833e83d2e404dd5de36df
  Compiled from "EnumTest.java"
public final class EnumTest extends java.lang.Enum<EnumTest>
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_FINAL, ACC_SUPER, ACC_ENUM
Constant pool:
    ...... // 省略此处的常量池
{
  public static final EnumTest enum1;
    descriptor: LEnumTest;
    flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM

  public static final EnumTest enum2;
    descriptor: LEnumTest;
    flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM

  static {};
    descriptor: ()V
    flags: ACC_STATIC
    Code:
      stack=6, locals=0, args_size=0
         0: new           #1                  // class EnumTest
         3: dup
         4: ldc           #16                 // String enum1
         6: iconst_0
         7: iconst_1
         8: iconst_1
         9: invokespecial #17                 // Method "<init>":(Ljava/lang/String;III)V
        12: putstatic     #21                 // Field enum1:LEnumTest;
        15: new           #1                  // class EnumTest
        18: dup
        19: ldc           #23                 // String enum2
        21: iconst_1
        22: iconst_2
        23: iconst_2
        24: invokespecial #17                 // Method "<init>":(Ljava/lang/String;III)V
        27: putstatic     #24                 // Field enum2:LEnumTest;
        30: iconst_2
        31: anewarray     #1                  // class EnumTest
        34: dup
        35: iconst_0
        36: getstatic     #21                 // Field enum1:LEnumTest;
        39: aastore
        40: dup
        41: iconst_1
        42: getstatic     #24                 // Field enum2:LEnumTest;
        45: aastore
        46: putstatic     #26                 // Field ENUM$VALUES:[LEnumTest;
        49: return
      LineNumberTable:
        line 3: 0
        line 2: 30
      LocalVariableTable:
        Start  Length  Slot  Name   Signature

  public static EnumTest[] values();
    descriptor: ()[LEnumTest;
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=5, locals=3, args_size=0
         0: getstatic     #26                 // Field ENUM$VALUES:[LEnumTest;
         3: dup
         4: astore_0
         5: iconst_0
         6: aload_0
         7: arraylength
         8: dup
         9: istore_1
        10: anewarray     #1                  // class EnumTest
        13: dup
        14: astore_2
        15: iconst_0
        16: iload_1
        17: invokestatic  #40                 // Method java/lang/System.arraycopy:(Ljava/lang/Object;ILjava/lang/Object;II)V
        20: aload_2
        21: areturn
      LineNumberTable:
        line 1: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature

  public static EnumTest valueOf(java.lang.String);
    descriptor: (Ljava/lang/String;)LEnumTest;
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=1, args_size=1
         0: ldc           #1                  // class EnumTest
         2: aload_0
         3: invokestatic  #48                 // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
         6: checkcast     #1                  // class EnumTest
         9: areturn
      LineNumberTable:
        line 1: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
}
SourceFile: "EnumTest.java"
Signature: #54                          // Ljava/lang/Enum<LEnumTest;>;

可以看到jvm将对应的Enum类型编译成了同名的类,并且该类继承了java.lang.Enum。然后定义了两个静态的属性,属性名分别为enum1,enum2,它们的类型都是类EnumTest。这两个属性正好对应了枚举类型中定义的两个枚举值,它们的初始化在static静态块中。由于它们是static和public的,所以不需要实例化,直接通过EnumTest就可以直接调用,这与Enum的使用方式不谋而合。

来看看这两个属性的初始化过程,以enum1作为例子,

         0: new           #1                  // class EnumTest
         3: dup
         4: ldc           #16                 // String enum1
         6: iconst_0
         7: iconst_1
         8: iconst_1
         9: invokespecial #17                 // Method "<init>":(Ljava/lang/String;III)V
        12: putstatic     #21                 // Field enum1:LEnumTest;

1、调用new实例化一个EnumTest对象
2、dup将该对象指针压到操作数栈中
3、ldc将字符串“enum1”压到操作数栈中
4、iconst_0将整数0压到操作数栈中
5、iconst_1将整数1压到操作数栈中
6、iconst_1将整数1压到操作数栈中
7、invokespecial调用初始化方法初始化对应的属性。可以看到构造函数的参数类型为(String, int, int, int),根据其父类的构造函数的参数类型为(String, int)可以推断出:

前两个参数为枚举值的名字和枚举值对应的整数值,第三个参数和第四个参数为枚举中自定义的两个属性

那么为什么这个四个参数的构造函数在上面的反编译的内容中没有看到呢?原因就是该构造函数是私有的。为了验证这个结论我们用一个简单的类来验证这个结论,

public class ClassTest {
    public ClassTest(int i) {

    }

    private ClassTest(int i, int j) {

    }
}

javap之后,

Classfile /C:/Users/Administrator/workspace/Enum/bin/ClassTest.class
  Last modified 2015-7-29; size 378 bytes
  MD5 checksum 55d836f2782757c8dd94fd773c77e702
  Compiled from "ClassTest.java"
public class ClassTest
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
    ...... // 省略常量池
{
  public ClassTest(int);
    descriptor: (I)V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=2, args_size=2
         0: aload_0
         1: invokespecial #8                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 3: 0
        line 5: 4
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   LClassTest;
            0       5     1     i   I
}
SourceFile: "ClassTest.java"

可以看到只有公共的构造函数被打印出来了,而私有的两个参数的构造函数没有被打印出来。这也就解释了为什么Enum类型不能被实例化。

那么还有什么和普通的类不同呢?仔细看可以发现Enum编译出的类的flags为ACC_PUBLIC, ACC_FINAL, ACC_SUPER, ACC_ENUM,明显的比普通类多了一个ACC_ENUM。那么这个参数表明了什么呢?参看java虚拟机规范后可以看到它的意义,
这里写图片描述

然后再仔细看还能发现什么呢?似乎我们编写的Enum中没有values和valueOf这两个方法,而编译后的类中却有。可见这两个类是jvm在编译的时候自动加上的。来看看valueOf是怎么实现的。。

public class EnumUseTest {
    public static void main(String[] args) {
        EnumTest e = EnumTest.valueOf("enum1");
        System.out.println(e);
    }
}
         0: ldc           #1                  // class EnumTest
         2: aload_0
         3: invokestatic  #48                 // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
         6: checkcast     #1                  // class EnumTest
         9: areturn

主要看invokestatic指令,发现其调用了父类的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);
    }
    Map<String, T> enumConstantDirectory() {
        if (enumConstantDirectory == null) {
            T[] universe = getEnumConstantsShared();
            if (universe == null)
                throw new IllegalArgumentException(
                    getName() + " is not an enum type");
            Map<String, T> m = new HashMap<>(2 * universe.length);
            for (T constant : universe)
                m.put(((Enum<?>)constant).name(), constant);
            enumConstantDirectory = m;
        }
        return enumConstantDirectory;
    }

大概就是根据类名和枚举值的名字从Map中获取对应枚举值。

    T[] getEnumConstantsShared() {
        if (enumConstants == null) {
            if (!isEnum()) return null;
            try {
                final Method values = getMethod("values");  // 这里获取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); // 调用values方法
                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;
    }

调用values方法,也就是上面定义的方法,该方法从私有变量ENUM$VALUES中将定义好的几个枚举类型取出组成数组返回。而私有变量ENUM$VALUES的初始化实在静态初始块中进行的,当两个静态属性enum1和enum2被初始化后,放置到了ENUM$VALUES中。。。

Java中的枚举(enum)是一种特殊的数据类型,用于声明一个包含有限个数常量的集合。 使用关键字`enum`可以定义一个枚举类型。例如,我们可以定义一个表示星期几的枚举类型: ```java enum Day { MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY; } ``` 枚举类型的每个常量都是该类型的一个实例,可以通过初始变量列表来声明它们。在上面的例子中,我们定义了七个星期的常量。 枚举类型有一些重要的特性。首先,枚举常量是常量,并且在定义时已经确定。它们不可更改,也不可继承。其次,枚举常量是类型安全的,因为它们只能被分配给相应的枚举类型。例如,我们不能将一个星期常量分配给一个整数变量。 枚举类型还可以具有方法,像一个普通的类一样。例如,我们可以定义一个方法来获取星期几的缩写: ```java enum Day { MONDAY("Mon"), TUESDAY("Tue"), WEDNESDAY("Wed"), THURSDAY("Thu"), FRIDAY("Fri"), SATURDAY("Sat"), SUNDAY("Sun"); private final String abbreviation; private Day(String abbreviation) { this.abbreviation = abbreviation; } public String getAbbreviation() { return abbreviation; } } ``` 在上面的例子中,我们为每个枚举常量添加了一个缩写属性,并定义了一个返回该属性的方法。 通过使用枚举,我们可以将一组相关常量组织在一起,并可以轻松地进行类型安全的比较。枚举也可以在switch语句中使用,使代码更加可读和易于维护。 总结起来,Java中的枚举提供了一种简单而强大的方式来表示有限的常量集合。枚举常量是类型安全的,可以具有方法,可以在switch语句中使用,使代码更加清晰和可读。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值