深入理解枚举
最近刚学习完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)