深入理解枚举类

谁都会走 提交于 2019-12-05 09:36:11

深入理解枚举

​ 最近刚学习完JVM相关知识,想到枚举既然这么异类,那就从字节码角度来分析一下它。有关枚举的讲解,很多博客已经很详细了,这里我们就从字节码的角度重新来认识一下它。

​ 枚举类是一种特殊的类,它可以有自己的成员变量,方法,可以实现一个或多个接口,也可也定义自己的构造器。

1. 枚举类的继承结构:

2. 枚举类和普通类的区别:

(1)枚举类可以实现一个或多个接口,使用enum定义的枚举类默认继承了java.lang.Enum类,而不是继承Object类,因此枚举不能够显示继承其他父类(单继承局限性,但是可以实现接口)。其中“java.lang.Enum”实现了“Comparable”和“Serializable”接口。

(2)使用enum定义,非抽象的枚举类默认会使用final修饰,因此枚举类不能够派生子类。

(3)枚举类的构造器只能够使用private访问控制符,如果省略了构造器的访问控制符,则默认使用private修饰;如果强制指定访问控制符,则只能指定private修饰符。

(4)枚举类的所有实例必须要在枚举类的第一行显示列出,否则这个枚举类永远都能产生实例。列出这些实时,系统会自动添加public static final修饰符,无需程序员显示添加。

(5)枚举类默认提供了一个values方法,返回枚举实例数组,该方法可以很方便的遍历所有的枚举值。

为了能够更好的说明,上面的这些不同之处,下面我们定义了一个枚举类,使用“javap -c ”来反编译它

public enum  Season {
   SPRING("春"),SUMMER("夏"),AUTUMN("秋"),WINTTER("冬");
   private String name;

   Season(String name) {
      this.name = name;
   }
}

反编译可以得到这些信息:

"D:\Program Files\Java\jdk1.8.0_77\bin\javap.exe" -c com.bigdata.juc.enums.Season
Compiled from "Season.java"
public final class com.bigdata.juc.enums.Season extends java.lang.Enum<com.bigdata.juc.enums.Season> {
   //所定义的所有实例都变为了final修饰的了,并且是static类型的
  public static final com.bigdata.juc.enums.Season SPRING;

  public static final com.bigdata.juc.enums.Season SUMMER;

  public static final com.bigdata.juc.enums.Season AUTUMN;

  public static final com.bigdata.juc.enums.Season WINTTER;
  //自动提供了values方法
  public static com.bigdata.juc.enums.Season[] values();
    Code:
       0: getstatic     #1                  // Field $VALUES:[Lcom/bigdata/juc/enums/Season;
       3: invokevirtual #2                  // Method "[Lcom/bigdata/juc/enums/Season;".clone:()Ljava/lang/Object;
       6: checkcast     #3                  // class "[Lcom/bigdata/juc/enums/Season;"
       9: areturn
  //复写valueoff方法
  public static com.bigdata.juc.enums.Season valueOf(java.lang.String);
    Code:
       0: ldc           #4                  // class com/bigdata/juc/enums/Season
       2: aload_0
       3: invokestatic  #5                  // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
       6: checkcast     #4                  // class com/bigdata/juc/enums/Season
       9: areturn
//枚举类的对象实例化工作是在静态块中完成,无需用户手动实例化,也就是说上面的所定义的四个对象都是在静态块中完成实例化的
  static {};
    Code:
       //第一次,实例化枚举类并赋值给实例SPRING
       0: new           #4                  // class com/bigdata/juc/enums/Season
       3: dup
       4: ldc           #8                  // String SPRING
       6: iconst_0
       7: ldc           #9                  // String 春
       9: invokespecial #10                 // Method "<init>":(Ljava/lang/String;ILjava/lang/String;)V
      12: putstatic     #11                 // Field SPRING:Lcom/bigdata/juc/enums/Season;
      //第二次,实例化枚举类并赋值给实例SUMMER
      15: new           #4                  // class com/bigdata/juc/enums/Season
      18: dup
      19: ldc           #12                 // String SUMMER
      21: iconst_1
      22: ldc           #13                 // String 夏
      24: invokespecial #10                 // Method "<init>":(Ljava/lang/String;ILjava/lang/String;)V
      27: putstatic     #14                 // Field SUMMER:Lcom/bigdata/juc/enums/Season;
      //第三次,实例化枚举类并赋值给实例AUTUMN
      30: new           #4                  // class com/bigdata/juc/enums/Season
      33: dup
      34: ldc           #15                 // String AUTUMN
      36: iconst_2
      37: ldc           #16                 // String 秋
      39: invokespecial #10                 // Method "<init>":(Ljava/lang/String;ILjava/lang/String;)V
      42: putstatic     #17                 // Field AUTUMN:Lcom/bigdata/juc/enums/Season;
       //第四次,实例化枚举类并赋值给实例WINTTER
      45: new           #4                  // class com/bigdata/juc/enums/Season
      48: dup
      49: ldc           #18                 // String WINTTER
      51: iconst_3
      52: ldc           #19                 // String 冬
      54: invokespecial #10                 // Method "<init>":(Ljava/lang/String;ILjava/lang/String;)V
      57: putstatic     #20                 // Field WINTTER:Lcom/bigdata/juc/enums/Season;
      60: iconst_4
          //创建一个引用类型的数组,这个数组就是Season
      61: anewarray     #4                  // class com/bigdata/juc/enums/Season
          //复制栈顶一个字长的数据,将复制后的数据压栈。
      64: dup
          //0(int)值入栈。
      65: iconst_0
          //获取静态字段的值。
      66: getstatic     #11                 // Field SPRING:Lcom/bigdata/juc/enums/Season;
          //将栈顶引用类型值保存到指定引用类型数组的指定项。
      69: aastore
          //复制栈顶一个字长的数据,将复制后的数据压栈。
      70: dup
          //1(int)值入栈。
      71: iconst_1
          //获取静态字段的值
      72: getstatic     #14                 // Field SUMMER:Lcom/bigdata/juc/enums/Season;
          //将栈顶引用类型值保存到指定引用类型数组的指定项。
      75: aastore
      76: dup
      77: iconst_2
      78: getstatic     #17                 // Field AUTUMN:Lcom/bigdata/juc/enums/Season;
      81: aastore
      82: dup
      83: iconst_3
      84: getstatic     #20                 // Field WINTTER:Lcom/bigdata/juc/enums/Season;
      87: aastore
      88: putstatic     #1                  // Field $VALUES:[Lcom/bigdata/juc/enums/Season;
      91: return
}

Process finished with exit code 0

可以发现它在静态块中总共完成了两件事情:

(1)实例化枚举类并赋值给枚举实例

(2)创建引用类型的数组,并为数组赋值,这也是values方法能够得到枚举数组的原因。

由于枚举类是继承自“java.lang.Enum”类的,所以它自动的就继承了该类的一些方法:

引用自“疯狂JAVA讲义 李刚”

3. 枚举类的成员变量,方法和构造器

枚举类也是一种类,只是它是一种比较特殊的类,因此它一样可定义成员变量,方法和构造器。还是上面的一段代码

public enum  Season {
    //枚举实例,必须要定义在第一行
   SPRING("春"),SUMMER("夏"),AUTUMN("秋"),WINTTER("冬");
    //定义了成员变量
   private String name;
   //定义了构造器,默认为private类型,无论是否显示修饰
   Season(String name) {
      this.name = name;
   }
}

实际上它底层自动完成了实例化工作,前面我们通过反编译也看到了这一点,它是在静态块中完成对象的实例化工作的。类似于这种:

public static final  SPRING=new SPRING("春");
public static final  SPRING=new SUMMER("夏");
public static final  SPRING=new AUTUMN("秋");
public static final  SPRING=new WINTTER("冬");

4. 枚举类实现接口

​ 枚举虽然无法继承其他类,但是还是可以实现其他接口的,这是因为接口没有单继承的局限性。枚举类在实现接口上和普通类的实现接口是一样的,没有什么本质区别。

定义接口:

public interface GenderDesc {
    void info();
}

枚举类实现该接口:

//实现接口,并且以内部类的形式
public enum Gender implements GenderDesc
{
    // public static final Gender MALE = new Gender("男");
    MALE("男") {
        public void info() {
            System.out.println("gender male information");
        }
    },
    FEMALE("女") {

        public void info() {
            System.out.println("gender female information");
        }
    };
    private String name;
    private Gender(String name) {
        this.name = name;
    }
}
public class enumTest {
    public static void main(String[] args) {
        // 通过valueof方法获取指定枚举类的值
        Gender gf = Enum.valueOf(Gender.class, "FEMALE");
        Gender gm = Enum.valueOf(Gender.class, "MALE");
        System.out.println(gf + " is stand for " + gf.getName());
        System.out.println(gm + " is stand for " + gm.getName());
        gf.info();
        gm.info();
    }
}

​ Gender类在编译的时候,会生成三个类“Gender.class”,“Gender$1.class”和“Gender$2.class”,其中Gender$1和Gender$2都是表示匿名内部类。

注1:关于上面所讲的非抽象的枚举类,使用“final”修饰,抽象的枚举类,使用abstract修饰,这点可以通过“javap -c Gender.class”看到:

D:\Project\JUCDmo\other\target\classes\com\bigdata\juc\enums>javap -c Gender.class
Compiled from "Gender.java"
//使用abstract修饰,因为尽管匿名内部实例MALE和FEMALE中实现了info方法,但是在Gender类中并没有实现info方法,所以它仍然会被认为是抽象的,除非显示的实现info方法
public abstract class com.bigdata.juc.enums.Gender extends java.lang.Enum<com.bigdata.juc.enums.Gender> implements com.bigdata.juc.enums.GenderDesc {
  public static final com.bigdata.juc.enums.Gender MALE;

  public static final com.bigdata.juc.enums.Gender FEMALE;

而“Gender$1.class”

D:\Project\JUCDmo\other\target\classes\com\bigdata\juc\enums>javap -p Gender$1.class
Compiled from "Gender.java"
//使用final修饰   
final class com.bigdata.juc.enums.Gender$1 extends com.bigdata.juc.enums.Gender {
  com.bigdata.juc.enums.Gender$1(java.lang.String, int, java.lang.String);
  public void info();
}

注2:两个枚举值的方法表现出不同的行为,指的是它们在info的输出结果上不同,其他表现行为没有什么特殊的。

原文链接:https://www.cnblogs.com/MarchThree/p/3720468.html

5. 包含抽象方法的枚举类

枚举类中定义抽象方法时,不能够使用abstract关键字将枚举定义为抽象类(因为系统会自动为它添加abstract关键字),但因为枚举类需要显示创建枚举值,所以定义每个枚举值时须为抽象方法提供实现,否则将出现编译异常。

enum Operation {
    PLUS {
        public double eval(double x, double y) {
            return x + y;
        }
    },
    MINS {
        public double eval(double x, double y) {
            return x - y;
        }
    },
    TIMES {
        public double eval(double x, double y) {
            return x * y;
        }
    },
    DIVIDE {
        public double eval(double x, double y) {
            if (y == 0) {
                return -1;
            }
            return x / y;
        }
    };
    //为枚举类定义抽象方法,具体由枚举值提供实现
    public abstract double eval(double x, double y);
}
public class OperationTest {
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        System.out.println(Operation.PLUS.eval(1, 2));
        System.out.println(Operation.DIVIDE.eval(1, 0));
    }
}

反编译后的部分结果:

D:\Project\JUCDmo\other\target\classes\com\bigdata\juc\enums>javap -c Operation.class
Compiled from "OperationTest.java"
//类名为abstract修饰    
abstract class com.bigdata.juc.enums.Operation extends java.lang.Enum<com.bigdata.juc.enums.Operation> {
  public static final com.bigdata.juc.enums.Operation PLUS;

  public static final com.bigdata.juc.enums.Operation MINS;

  public static final com.bigdata.juc.enums.Operation TIMES;

  public static final com.bigdata.juc.enums.Operation DIVIDE;

  public static com.bigdata.juc.enums.Operation[] values();
    Code:
       0: getstatic     #2                  // Field $VALUES:[Lcom/bigdata/juc/enums/Operation;
       3: invokevirtual #3                  // Method "[Lcom/bigdata/juc/enums/Operation;".clone:()Ljava/lang/Object;
       6: checkcast     #4                  // class "[Lcom/bigdata/juc/enums/Operation;"
       9: areturn

  public static com.bigdata.juc.enums.Operation valueOf(java.lang.String);
    Code:
       0: ldc           #5                  // class com/bigdata/juc/enums/Operation
       2: aload_0
       3: invokestatic  #6                  // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
       6: checkcast     #5                  // class com/bigdata/juc/enums/Operation
       9: areturn

  public abstract double eval(double, double);

  com.bigdata.juc.enums.Operation(java.lang.String, int, com.bigdata.juc.enums.Operation$1);
    Code:
       0: aload_0
       1: aload_1
       2: iload_2
       3: invokespecial #1                  // Method "<init>":(Ljava/lang/String;I)V
       6: return

  static {};
    ……

原文链接:https://blog.csdn.net/uni_ys/article/details/60956718 https://www.cnblogs.com/MarchThree/p/3720467.html

6. 枚举的用途简介

在单例中,使用枚举

```java
package com.bigdata.juc.singleton;
/*
 * 枚举类型:表示该类型的对象是有限的几个
 * 我们可以限定为一个,就成了单例
 */
enum EnumSingleton{
    //等价于 public static final INSTANCE=new EnumSingleton();
    INSTANCE
}
public class Singleton2 {
    public static void main(String[] args) {
        EnumSingleton s = EnumSingleton.INSTANCE;
        System.out.println(s);
    }
}
```

7. 关于枚举类中的values方法?

​ 枚举的values(),既不是来自于在Java.lang.Enum,也不是来自于Enum所实现的接口,它是在编译过程中自己产生的,在前面的反编译过程中,我们也看到了这点,而且我们也看到它底层实际上就是生成了一个引用类型的数组,然后values方法返回了这个枚举数组。关于它的使用可以参考枚举类enum的values()方法

​ 想要深入理解它的来源,可以参考该博客深入理解Java枚举类型(enum)

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!