定义枚举类
//定义加减乘除四个枚举常量public enum Operator {
ADD,SUBTRACT,MULTIPLY,DIVIDE
}可以在枚举中定义普通方法和抽象方法.
public enum Operator {
ADD { @Override
public int calculate(int a, int b) { return a + b;
}
}, SUBTRACT { @Override
public int calculate(int a, int b) { return a - b;
}
}, MULTIPLY { @Override
public int calculate(int a, int b) { return a * b;
}
}, DIVIDE { @Override
public int calculate(int a, int b) { if (b == 0) throw new IllegalArgumentException("divisor must be not zero"); return a / b;
}
}; public abstract int calculate(int a, int b);
}也同样可在枚举类中定义属性,构造方法.
public enum Operator {
ADD("+") { @Override
public int calculate(int a, int b) { return a + b;
}
}, SUBTRACT("-") { @Override
public int calculate(int a, int b) { return a - b;
}
}, MULTIPLY("*") { @Override
public int calculate(int a, int b) { return a * b;
}
}, DIVIDE("/") { @Override
public int calculate(int a, int b) { if (b == 0) throw new IllegalArgumentException("divisor must be not zero"); return a / b;
}
}; private String operator;
Operator(String operator) { this.operator = operator;
} public abstract int calculate(int a, int b); public String getOperator() { return operator;
}
}实现原理
Java文件在编译后会生成.class字节码文件.
对该文件使用javap xxx.class命令可以进行查看.
Compiled from "Operator.java"public abstract class com.inkio.onepro.Operator extends java.lang.Enum<com.inkio.onepro.Operator> { public static final com.inkio.onepro.Operator ADD; public static final com.inkio.onepro.Operator SUBTRACT; public static final com.inkio.onepro.Operator MULTIPLY; public static final com.inkio.onepro.Operator DIVIDE; public static com.inkio.onepro.Operator[] values(); public static com.inkio.onepro.Operator valueOf(java.lang.String); public abstract int calculate(int, int); public java.lang.String getOperator();
com.inkio.onepro.Operator(java.lang.String, int, java.lang.String, com.inkio.onepro.Operator$1); static {};
}可以看到,枚举类在经过编译后变成了一个抽象类,继承了
java.lang.Enum类;我们所定义的枚举常量也变成了
public static final属性,而且其类型为这个抽象类的类型,名字则还是枚举常量的名字.并且同时我们可以看见在我们的编译路径下看见四个内部类的
.class文件.以及values()和valutOf()方法.image.png
构造方法由我们定义的两个参数变成三个参数.
生成一个静态代码块.
我们可以通过javap -c -v Operator.class指令对文件进行反编译如下.
Classfile /Users/sean/Desktop/Enum/Operator.class
Last modified Dec 20, 2018; size 1587 bytes
MD5 checksum 0b06630621a6a49772e53495294f72cf
Compiled from "Operator.java"public abstract class com.inkio.onepro.Operator extends java.lang.Enum<com.inkio.onepro.Operator> minor version: 0 major version: 52 flags: ACC_PUBLIC, ACC_SUPER, ACC_ABSTRACT, ACC_ENUMConstant pool:
#1 = Methodref #5.#60 // com/inkio/onepro/Operator."<init>":(Ljava/lang/String;ILjava/lang/String;)V
#2 = Fieldref #5.#61 // com/inkio/onepro/Operator.$VALUES:[Lcom/inkio/onepro/Operator;
#3 = Methodref #62.#63 // "[Lcom/inkio/onepro/Operator;".clone:()Ljava/lang/Object;
#4 = Class #39 // "[Lcom/inkio/onepro/Operator;"
#5 = Class #64 // com/inkio/onepro/Operator
#6 = Methodref #29.#65 // java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
#7 = Methodref #29.#66 // java/lang/Enum."<init>":(Ljava/lang/String;I)V
#8 = Fieldref #5.#67 // com/inkio/onepro/Operator.operator:Ljava/lang/String;
#9 = Class #68 // com/inkio/onepro/Operator$1
#10 = String #31 // ADD
#11 = String #69 // +
#12 = Methodref #9.#60 // com/inkio/onepro/Operator$1."<init>":(Ljava/lang/String;ILjava/lang/String;)V
#13 = Fieldref #5.#70 // com/inkio/onepro/Operator.ADD:Lcom/inkio/onepro/Operator;
#14 = Class #71 // com/inkio/onepro/Operator$2
#15 = String #33 // SUBTRACT
#16 = String #72 // -
#17 = Methodref #14.#60 // com/inkio/onepro/Operator$2."<init>":(Ljava/lang/String;ILjava/lang/String;)V
#18 = Fieldref #5.#73 // com/inkio/onepro/Operator.SUBTRACT:Lcom/inkio/onepro/Operator;
#19 = Class #74 // com/inkio/onepro/Operator$3
#20 = String #34 // MULTIPLY
#21 = String #75 // *
#22 = Methodref #19.#60 // com/inkio/onepro/Operator$3."<init>":(Ljava/lang/String;ILjava/lang/String;)V
#23 = Fieldref #5.#76 // com/inkio/onepro/Operator.MULTIPLY:Lcom/inkio/onepro/Operator;
#24 = Class #77 // com/inkio/onepro/Operator$4
#25 = String #35 // DIVIDE
#26 = String #78 // /
#27 = Methodref #24.#60 // com/inkio/onepro/Operator$4."<init>":(Ljava/lang/String;ILjava/lang/String;)V
#28 = Fieldref #5.#79 // com/inkio/onepro/Operator.DIVIDE:Lcom/inkio/onepro/Operator;
#29 = Class #80 // java/lang/Enum
#30 = Utf8 InnerClasses
#31 = Utf8 ADD
#32 = Utf8 Lcom/inkio/onepro/Operator;
#33 = Utf8 SUBTRACT
#34 = Utf8 MULTIPLY
#35 = Utf8 DIVIDE
#36 = Utf8 operator
#37 = Utf8 Ljava/lang/String;
#38 = Utf8 $VALUES
#39 = Utf8 [Lcom/inkio/onepro/Operator;
#40 = Utf8 values
#41 = Utf8 ()[Lcom/inkio/onepro/Operator;
#42 = Utf8 Code
#43 = Utf8 LineNumberTable
#44 = Utf8 valueOf
#45 = Utf8 (Ljava/lang/String;)Lcom/inkio/onepro/Operator;
#46 = Utf8 <init>
#47 = Utf8 (Ljava/lang/String;ILjava/lang/String;)V
#48 = Utf8 Signature
#49 = Utf8 (Ljava/lang/String;)V
#50 = Utf8 calculate
#51 = Utf8 (II)I
#52 = Utf8 getOperator
#53 = Utf8 ()Ljava/lang/String;
#54 = Utf8 (Ljava/lang/String;ILjava/lang/String;Lcom/inkio/onepro/Operator$1;)V
#55 = Utf8 <clinit>
#56 = Utf8 ()V
#57 = Utf8 Ljava/lang/Enum<Lcom/inkio/onepro/Operator;>;
#58 = Utf8 SourceFile
#59 = Utf8 Operator.java
#60 = NameAndType #46:#47 // "<init>":(Ljava/lang/String;ILjava/lang/String;)V
#61 = NameAndType #38:#39 // $VALUES:[Lcom/inkio/onepro/Operator;
#62 = Class #39 // "[Lcom/inkio/onepro/Operator;"
#63 = NameAndType #81:#82 // clone:()Ljava/lang/Object;
#64 = Utf8 com/inkio/onepro/Operator
#65 = NameAndType #44:#83 // valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
#66 = NameAndType #46:#84 // "<init>":(Ljava/lang/String;I)V
#67 = NameAndType #36:#37 // operator:Ljava/lang/String;
#68 = Utf8 com/inkio/onepro/Operator$1
#69 = Utf8 +
#70 = NameAndType #31:#32 // ADD:Lcom/inkio/onepro/Operator;
#71 = Utf8 com/inkio/onepro/Operator$2
#72 = Utf8 -
#73 = NameAndType #33:#32 // SUBTRACT:Lcom/inkio/onepro/Operator;
#74 = Utf8 com/inkio/onepro/Operator$3
#75 = Utf8 *
#76 = NameAndType #34:#32 // MULTIPLY:Lcom/inkio/onepro/Operator;
#77 = Utf8 com/inkio/onepro/Operator$4
#78 = Utf8 /
#79 = NameAndType #35:#32 // DIVIDE:Lcom/inkio/onepro/Operator;
#80 = Utf8 java/lang/Enum
#81 = Utf8 clone
#82 = Utf8 ()Ljava/lang/Object;
#83 = Utf8 (Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
#84 = Utf8 (Ljava/lang/String;I)V
{ public static final com.inkio.onepro.Operator ADD;
descriptor: Lcom/inkio/onepro/Operator;
flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM public static final com.inkio.onepro.Operator SUBTRACT;
descriptor: Lcom/inkio/onepro/Operator;
flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM public static final com.inkio.onepro.Operator MULTIPLY;
descriptor: Lcom/inkio/onepro/Operator;
flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM public static final com.inkio.onepro.Operator DIVIDE;
descriptor: Lcom/inkio/onepro/Operator;
flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM public static com.inkio.onepro.Operator[] values();
descriptor: ()[Lcom/inkio/onepro/Operator;
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=0, args_size=0
0: getstatic #2 // Field $VALUES:[Lcom/inkio/onepro/Operator;
3: invokevirtual #3 // Method "[Lcom/inkio/onepro/Operator;".clone:()Ljava/lang/Object;
6: checkcast #4 // class "[Lcom/inkio/onepro/Operator;"
9: areturn
LineNumberTable:
line 3: 0
public static com.inkio.onepro.Operator valueOf(java.lang.String);
descriptor: (Ljava/lang/String;)Lcom/inkio/onepro/Operator;
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=1, args_size=1
0: ldc #5 // class com/inkio/onepro/Operator
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/inkio/onepro/Operator
9: areturn
LineNumberTable:
line 3: 0
public abstract int calculate(int, int);
descriptor: (II)I
flags: ACC_PUBLIC, ACC_ABSTRACT public java.lang.String getOperator();
descriptor: ()Ljava/lang/String;
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0 1: getfield #8 // Field operator:Ljava/lang/String;
4: areturn
LineNumberTable:
line 37: 0
com.inkio.onepro.Operator(java.lang.String, int, java.lang.String, com.inkio.onepro.Operator$1);
descriptor: (Ljava/lang/String;ILjava/lang/String;Lcom/inkio/onepro/Operator$1;)V
flags: ACC_SYNTHETIC
Code:
stack=4, locals=5, args_size=5
0: aload_0 1: aload_1 2: iload_2 3: aload_3 4: invokespecial #1 // Method "<init>":(Ljava/lang/String;ILjava/lang/String;)V
7: return
LineNumberTable:
line 3: 0
static {};
descriptor: ()V
flags: ACC_STATIC
Code:
stack=5, locals=0, args_size=0
0: new #9 // class com/inkio/onepro/Operator$1
3: dup 4: ldc #10 // String ADD
6: iconst_0 7: ldc #11 // String +
9: invokespecial #12 // Method com/inkio/onepro/Operator$1."<init>":(Ljava/lang/String;ILjava/lang/String;)V
12: putstatic #13 // Field ADD:Lcom/inkio/onepro/Operator;
15: new #14 // class com/inkio/onepro/Operator$2
18: dup 19: ldc #15 // String SUBTRACT
21: iconst_1 22: ldc #16 // String -
24: invokespecial #17 // Method com/inkio/onepro/Operator$2."<init>":(Ljava/lang/String;ILjava/lang/String;)V
27: putstatic #18 // Field SUBTRACT:Lcom/inkio/onepro/Operator;
30: new #19 // class com/inkio/onepro/Operator$3
33: dup 34: ldc #20 // String MULTIPLY
36: iconst_2 37: ldc #21 // String *
39: invokespecial #22 // Method com/inkio/onepro/Operator$3."<init>":(Ljava/lang/String;ILjava/lang/String;)V
42: putstatic #23 // Field MULTIPLY:Lcom/inkio/onepro/Operator;
45: new #24 // class com/inkio/onepro/Operator$4
48: dup 49: ldc #25 // String DIVIDE
51: iconst_3 52: ldc #26 // String /
54: invokespecial #27 // Method com/inkio/onepro/Operator$4."<init>":(Ljava/lang/String;ILjava/lang/String;)V
57: putstatic #28 // Field DIVIDE:Lcom/inkio/onepro/Operator;
60: iconst_4 61: anewarray #5 // class com/inkio/onepro/Operator
64: dup 65: iconst_0 66: getstatic #13 // Field ADD:Lcom/inkio/onepro/Operator;
69: aastore 70: dup 71: iconst_1 72: getstatic #18 // Field SUBTRACT:Lcom/inkio/onepro/Operator;
75: aastore 76: dup 77: iconst_2 78: getstatic #23 // Field MULTIPLY:Lcom/inkio/onepro/Operator;
81: aastore 82: dup 83: iconst_3 84: getstatic #28 // Field DIVIDE:Lcom/inkio/onepro/Operator;
87: aastore 88: putstatic #2 // Field $VALUES:[Lcom/inkio/onepro/Operator;
91: return
LineNumberTable:
line 5: 0
line 10: 15
line 15: 30
line 20: 45
line 3: 60}
Signature: #57 // Ljava/lang/Enum<Lcom/inkio/onepro/Operator;>;SourceFile: "Operator.java"InnerClasses: static #24; //class com/inkio/onepro/Operator$4
static #19; //class com/inkio/onepro/Operator$3
static #14; //class com/inkio/onepro/Operator$2
static #9; //class com/inkio/onepro/Operator$1代码那么多,看起来比较繁琐,那么我们翻译一下,使用jad将这个文件转成java的.执行指令jad -sjava Operator.class
// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.// Jad home page: http://www.kpdus.com/jad.html// Decompiler options: packimports(3) // Source File Name: Operator.javapackage com.inkio.onepro;public abstract class Operator extends Enum{ public static Operator[] values()
{ return (Operator[])$VALUES.clone();
} public static Operator valueOf(String s)
{ return (Operator)Enum.valueOf(com/inkio/onepro/Operator, s);
} private Operator(String s, int i, String s1)
{ super(s, i);
operator = s1;
} public abstract int calculate(int i, int j); public String getOperator()
{ return operator;
} public static final Operator ADD; public static final Operator SUBTRACT; public static final Operator MULTIPLY; public static final Operator DIVIDE; private String operator; private static final Operator $VALUES[]; static
{
ADD = new Operator("ADD", 0, "+") { public int calculate(int i, int j)
{ return i + j;
}
}
;
SUBTRACT = new Operator("SUBTRACT", 1, "-") { public int calculate(int i, int j)
{ return i - j;
}
}
;
MULTIPLY = new Operator("MULTIPLY", 2, "*") { public int calculate(int i, int j)
{ return i * j;
}
}
;
DIVIDE = new Operator("DIVIDE", 3, "/") { public int calculate(int i, int j)
{ if(j == 0) throw new IllegalArgumentException("divisor must be not zero"); else
return i / j;
}
}
;
$VALUES = (new Operator[] {
ADD, SUBTRACT, MULTIPLY, DIVIDE
});
}
}这下代码是不是变得高端大次上档气了,嘿嘿,那么继续分析~
首先,声明了四个public static final常量,并且在静态代码块中为其赋值,且静态代码块中还生成了一个$VALUES字段,并初始化为定义的枚举类型的所有枚举常量的数组.
并且声明了一个枚举类型数组的静态方法values()以及一个我们定义的枚举类型的静态方法valueOf(),我们可以通过values()方法获取这个枚举值的数组,它的实现就是在静态代码块中生成的$VALUES并初始化的过程,同样也可以直接通过valueOf方法得到定义的枚举类型常量,是通过强转实现的.
可以这样使用:
Operator operator = Operator.valueOf(Operator.ADD.name()); System.out.println(operator); >>> ADD
再说说构造方法,明明我们定义的时候只有一个参数,为什么现在变成三个了呢?
其实我们在使用时可以发现,每个枚举类其实都定义了两个属性,一个name一个ordinal,
Operator.ADD.name()Operator.ADD.ordinal()
name表示我们定义枚举常量的名称,如ADD、SUBTRACT.ordinal表示的是一个顺序号,按照我们定义常量的顺序自动分配,从0开始,在枚举常量初始化时,会自动初始化这两个字段设置相应的值,再加上我们为构造函数设置的字段,所以构造方法就有三个参数了.
我们可以从Enum类的代码中看到,定义的name和ordinal属性都是final的,而且大部分方法也都是final的,特别是clone、readObject、writeObject这三个方法,这三个方法和枚举通过静态代码块来进行初始化一起,它保证了枚举类型的不可变性,不能通过克隆,不能通过序列化和反序列化来复制枚举,这能保证一个枚举常量只是一个实例,即是单例的,所以在effective java中推荐使用枚举来实现单例.
对于Android开发,枚举还有一个面试相关的问题,在Android中为什么不推荐使用枚举,说出原因.通过本文其实我们就可以知道,枚举占用的内存会多很多.所以推荐使用注解来代替枚举.@IntDef、@StringDef等.
作者:Inkio
链接:https://www.jianshu.com/p/fe7891daeb36