深入理解枚举类
深入理解枚举
最近刚学习完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 -v -p ”来反编译它
enum Season {
SPRING("春"),SUMMER("夏"),AUTUMN("秋"),WINTTER("冬");
private String name;
Season(String name) {
this.name = name;
}
}
反编译可以得到这些信息:
$ javap -v -p Season.class
Classfile /D:/Project/JvmDemo/target/classes/com/bigdata/java/Season.class
Last modified 2020-3-21; size 1206 bytes
MD5 checksum e78087beee7e634071bc6cd1e019c168
Compiled from "Demo69.java"
final class com.bigdata.java.Season extends java.lang.Enum<com.bigdata.java.Season>
minor version: 0
major version: 52
flags: ACC_FINAL, ACC_SUPER, ACC_ENUM
Constant pool:
...
{
//枚举类中定义的常量,在字节码层面上的反映
public static final com.bigdata.java.Season SPRING;
descriptor: Lcom/bigdata/java/Season;
flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
public static final com.bigdata.java.Season SUMMER;
descriptor: Lcom/bigdata/java/Season;
flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
public static final com.bigdata.java.Season AUTUMN;
descriptor: Lcom/bigdata/java/Season;
flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
public static final com.bigdata.java.Season WINTTER;
descriptor: Lcom/bigdata/java/Season;
flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
private java.lang.String name;
descriptor: Ljava/lang/String;
flags: ACC_PRIVATE
//枚举数组,这个数组供values方法使用,用于返回枚对象数组
private static final com.bigdata.java.Season[] $VALUES;
descriptor: [Lcom/bigdata/java/Season;
flags: ACC_PRIVATE, ACC_STATIC, ACC_FINAL, ACC_SYNTHETIC
//自动生成了values方法,它所完成的功能就是$VALUES.clone
public static com.bigdata.java.Season[] values();
descriptor: ()[Lcom/bigdata/java/Season;
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=0, args_size=0
//从类中获取静态字段$VALUES,然后压入到操作数栈的栈顶
0: getstatic #1 // Field $VALUES:[Lcom/bigdata/java/Season;
//调用实例方法clone,即出栈$VALUES,然后执行$VALUES.clone,由于clone是native方法并且有返回值,所以返回值会被压入到操作数的栈顶,此时操作数的栈顶是枚举对象数组
3: invokevirtual #2 // Method "[Lcom/bigdata/java/Season;".clone:()Ljava/lang/Object;
//弹出栈顶的枚举对象数组,检查它的类型是否符合给定的类型,检查完毕后再次压入到操作数栈的栈顶
6: checkcast #3 // class "[Lcom/bigdata/java/Season;"
//将操作数栈顶的值弹出,并返回到给调用处,这样返回的就是一个clone后的枚举对象数组
9: areturn
LineNumberTable:
line 9: 0
//它所完成的功能就是根据传入的字符串,调用父类的valueOf方法返回对应的枚举常量
public static com.bigdata.java.Season valueOf(java.lang.String);
descriptor: (Ljava/lang/String;)Lcom/bigdata/java/Season;
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=1, args_size=1
0: ldc #4 // class com/bigdata/java/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/java/Season
9: areturn
LineNumberTable:
line 9: 0
LocalVariableTable:
Start Length Slot Name Signature
0 10 0 name Ljava/lang/String;
//枚举构造方法,接收String和int,String,第一个String是枚举常量字符串,第二个是枚举常量所定义的位置,第三个枚举中构造方法传入的值
private com.bigdata.java.Season(java.lang.String);
descriptor: (Ljava/lang/String;ILjava/lang/String;)V
flags: ACC_PRIVATE
Code:
stack=3, locals=4, args_size=4
//将本地变量表索引0上的引用类型元素压入到操作数的栈顶
0: aload_0
//将本地变量表索引1上的引用类型元素压入到操作数的栈顶。尽管此时本地变量表对应索引上,元素为空,但运行时局部变量表被被填充为枚举常量字符串
1: aload_1
//本地变量表索引2上的int型元素压入到操作数的栈顶,运行时本地变量表对应索引上会被填充为枚举常量定义的位置
2: iload_2
//从操作数栈中依次弹出说压入的值,然后调用父类的构造方法,Enum."<init>":(Ljava/lang/String;I)V
3: invokespecial #6 // Method java/lang/Enum."<init>":(Ljava/lang/String;I)V
//将本地变量表索引0上的引用类型元素压入到操作数的栈顶
6: aload_0
//将本地变量表索引3上的引用类型元素压入到操作数的栈顶
7: aload_3
//为对象中的字段设置值,从操作数栈中依次弹出枚举常量和构造方法所传入的值,为name字段赋值,如:SEASON.SPRING.name=“春”
8: putfield #7 // Field name:Ljava/lang/String;
11: return
LineNumberTable:
line 13: 0
line 14: 6
line 15: 11
LocalVariableTable:
Start Length Slot Name Signature
0 12 0 this Lcom/bigdata/java/Season;
0 12 3 name Ljava/lang/String;
Signature: #42 // (Ljava/lang/String;)V
static {};
descriptor: ()V
flags: ACC_STATIC
Code:
stack=5, locals=0, args_size=0
0: new #4 // class com/bigdata/java/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/java/Season;
15: new #4 // class com/bigdata/java/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/java/Season;
30: new #4 // class com/bigdata/java/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/java/Season;
45: new #4 // class com/bigdata/java/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/java/Season;
60: iconst_4
61: anewarray #4 // class com/bigdata/java/Season
64: dup
65: iconst_0
66: getstatic #11 // Field SPRING:Lcom/bigdata/java/Season;
69: aastore
70: dup
71: iconst_1
72: getstatic #14 // Field SUMMER:Lcom/bigdata/java/Season;
75: aastore
76: dup
77: iconst_2
78: getstatic #17 // Field AUTUMN:Lcom/bigdata/java/Season;
81: aastore
82: dup
83: iconst_3
84: getstatic #20 // Field WINTTER:Lcom/bigdata/java/Season;
87: aastore
88: putstatic #1 // Field $VALUES:[Lcom/bigdata/java/Season;
91: return
LineNumberTable:
line 10: 0
line 9: 60
}
Signature: #45 // Ljava/lang/Enum<Lcom/bigdata/java/Season;>;
SourceFile: "Demo69.java"
可以发现它在静态块中总共完成了两件事情:
(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的输出结果上不同,其他表现行为没有什么特殊的。
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 {};
……
6. 枚举的用途简介
在单例中,使用枚举
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所实现的接口,它是在编译过程中自己产生的,在前面的反编译过程中,我们也看到了这点,而且我们也看到它底层实际上就是生成了一个引用类型的数组(clone得到的),然后values方法返回了这个枚举数组。关于它的使用可以参考枚举类enum的values()方法
想要深入理解它的来源,可以参考该博客
http://blog.sina.com.cn/s/blog_6fd0fd4b01014x8l.html
8. 使用枚举实现单例设计
public class SingletonObject7 {
private SingletonObject7() {
}
//定义枚举内部类
private enum Singleton {
INSTANCE;
private final SingletonObject7 instance;
Singleton() {
instance = new SingletonObject7();
}
public SingletonObject7 getInstance() {
return instance;
}
}
public static SingletonObject7 getInstance() {
return Singleton.INSTANCE.getInstance();
}
public static void main(String[] args) {
IntStream.rangeClosed(1, 100)
.forEach(i -> new Thread(String.valueOf(i)) {
@Override
public void run() {
System.out.println(SingletonObject7.getInstance());
}
}.start());
}
}
9. 枚举为什么能够防止反射攻击
观察如下实例:
public enum SingletonClass {
INSTANCE;
private String name;
public void test() {
System.out.println("The Test!");
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public static void main(String[] args) {
System.out.println(SingletonClass.INSTANCE);
try {
Class<SingletonClass> aClass = SingletonClass.class;
Constructor<SingletonClass> constructor = aClass.getDeclaredConstructor(null);
constructor.setAccessible(true);
constructor.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
}
}
运行结果:
INSTANCE
java.lang.NoSuchMethodException: com.bigdata.java.SingletonClass.<init>()
at java.lang.Class.getConstructor0(Class.java:3082)
at java.lang.Class.getDeclaredConstructor(Class.java:2178)
at com.bigdata.java.SingletonClass.main(SingletonClass.java:28)
- 为什么会报找不到“SingletonClass.<init>()”方法异常?
为了解答这个问题,我们需要对该枚举类执行反编译,在反编译后能够看到这样一个构造方法“com.bigdata.java.SingletonClass(Ljava/lang/String;I)V”
private com.bigdata.java.SingletonClass();
descriptor: (Ljava/lang/String;I)V
flags: ACC_PRIVATE
Code:
stack=3, locals=3, args_size=3
0: aload_0
1: aload_1
2: iload_2
3: invokespecial #6 // Method java/lang/Enum."<init>":(Ljava/lang/String;I)V
6: return
LineNumberTable:
line 7: 0
LocalVariableTable:
Start Length Slot Name Signature
0 7 0 this Lcom/bigdata/java/SingletonClass;
Signature: #39 // ()V
descriptor: (Ljava/lang/String;I)V,这说明SingletonClass构造方法的第一个参数为String类型,第二个参数为int类型,而它最终调用了父类中的构造方法:java/lang/Enum."<init>":(Ljava/lang/String;I)V
也即:java.lang.Enum#Enum
/**单独的构造方法。程序员无法调用此构造方法。该构造方法用于由响应枚举类型声明的编译器发出的代码。
* Sole constructor. Programmers cannot invoke this constructor.
* It is for use by code emitted by the compiler in response to
* enum type declarations.
*
* @param name - The name of this enum constant, which is the identifier
* used to declare it.
此枚举常量的名称,它是用来声明该常量的标识符。
* @param ordinal - The ordinal of this enumeration constant (its position
* in the enum declaration, where the initial constant is assigned
* an ordinal of zero).
枚举常量的序数(它在枚举声明中的位置,其中初始常量序数为零)。
*/
protected Enum(String name, int ordinal) {
this.name = name;
this.ordinal = ordinal;
}
那么“com.bigdata.java.SingletonClass(Ljava/lang/String;I)V”何时被调用的呢?再看反编译后的static块:
static {};
descriptor: ()V
flags: ACC_STATIC
Code:
stack=4, locals=0, args_size=0
0: new #4 // class com/bigdata/java/SingletonClass
//创建SingletonClass实例并放置到操作数栈的栈顶
3: dup
//复制操作数栈顶的值,并将复制后的值压入到操作数栈的栈顶,此时栈中有两个SingletonClass类的对象
4: ldc #19 // String INSTANCE
//将“INSTANCE”从运行时常量池中取出,并放置到操作数栈的栈顶,此时栈中有三个元素
6: iconst_0
//将0压入到操作数栈的栈顶,此时栈中有四个元素
7: invokespecial #20 // Method "<init>":(Ljava/lang/String;I)V
//调用该类的构造方法"<init>":(Ljava/lang/String;I)V,在调用构造方法的时候,依次从栈顶弹出0,INSTANCE,实例,然后调用该实例的构造方法,传入参数“INSTANCE”和“0”,此时操作数栈的栈顶只有一个元素
10: putstatic #11 // Field INSTANCE:Lcom/bigdata/java/SingletonClass;
//从操作数栈顶弹出该类的引用,然后赋值给类中的静态字段INSTANCE
13: iconst_1
//将1压入到操作数栈的栈顶,此时操作数栈中有1个元素
14: anewarray #4 // class com/bigdata/java/SingletonClass
//创建引用类型的数组,将栈顶元素弹出,作为创建的数组的长度,也即数组的长度为1,然后将数组的引用arrayref(为了叙述方便,使用arrayref代替)压入到操作数栈中
17: dup
//复制栈顶的值,并压入到操作数栈中,此时栈中有两个元素
18: iconst_0
//将0压入到操作数栈中,此时栈中有三个元素
19: getstatic #11 // Field INSTANCE:Lcom/bigdata/java/SingletonClass;
//获取静态字段INSTANCE的值,并压入到操作数栈中,此时栈中有四个元素
22: aastore
//从操作数栈读取一个reference类型数据存入到数组中,即依次弹出INSTANCE,0,arrayref,然后将INSTANCE放入到数组arrayref的0号位置。然后将arrayref压入到操作数栈的栈顶,此时栈中有两个元素,都是arrayref。
23: putstatic #1 // Field $VALUES:[Lcom/bigdata/java/SingletonClass;
//从操作数栈顶弹出该类的引用,然后赋值给类中的静态字段$VALUES,此时操作数栈中只有一个元素arrayref了
26: return
//方法返回void,此时操作数栈中的所有元素都会被丢弃
LineNumberTable:
line 8: 0
line 7: 13
通过以上的分析,可以看到在static块中,完成了以下任务
- 创建SingletonClass的实例
- 为类中的静态字段INSTANCE赋值
- 创建数组并赋值给静态字段$VALUES
通过static块,能够看到在实例化类的时候,调用的是Method "<init>":(Ljava/lang/String;I)V 方法。再回到上面的问题中,由于没有生成无参的构造方法,只有这么一个构造方法,所以会给出“java.lang.NoSuchMethodException: com.bigdata.java.SingletonClass.
既然没有无参构造方法,那么我们能否通过调用Method "<init>":(Ljava/lang/String;I)V 构造方法来进行实例化呢?
如果这样来修改main方法
public static void main(String[] args) {
System.out.println(SingletonClass.INSTANCE);
try {
Class<SingletonClass> aClass = SingletonClass.class;
Constructor<SingletonClass> constructor = aClass.getDeclaredConstructor(java.lang.String.class,int.class);
constructor.newInstance("instance2",1);
} catch (Exception e) {
e.printStackTrace();
}
}
运行结果:
INSTANCE
java.lang.IllegalArgumentException: Cannot reflectively create enum objects
at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
at com.bigdata.java.SingletonClass.main(SingletonClass.java:30)
为什么会报这个异常,查看newInstance源码即可:
public T newInstance(Object ... initargs)
throws InstantiationException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException
{
...
if ((clazz.getModifiers() & Modifier.ENUM) != 0)
throw new IllegalArgumentException("Cannot reflectively create enum objects");
...
}
可以看到异常是上面代码抛出的,说明它不能够对于枚举类型进行反射创建枚举类的实例,也即API层面就限制住了不能通过反射实例化枚举实例。
10. 建议使用“Enum.getDeclaringClass”,而不是“Object.getClass”来获取枚举类的Class实例
枚举类实现了Comparable接口,实现了compareTo方法:
public abstract class Enum<E extends Enum<E>>
implements Comparable<E>, Serializable {
/**将此枚举与指定的order对象进行比较。返回一个负整数、零或正整数,因为该对象小于、等于或大于指定的对象
* Compares this enum with the specified object for order. Returns a
* negative integer, zero, or a positive integer as this object is less
* than, equal to, or greater than the specified object.
*枚举常数只能与同一枚举类型的其他枚举常数相比较。此方法实现的自然顺序与声明常量的顺序相同。
* Enum constants are only comparable to other enum constants of the
* same enum type. The natural order implemented by this
* method is the order in which the constants are declared.
*/
public final int compareTo(E o) {
Enum<?> other = (Enum<?>)o;
Enum<E> self = this;
if (self.getClass() != other.getClass() && // optimization
self.getDeclaringClass() != other.getDeclaringClass())
throw new ClassCastException();
return self.ordinal - other.ordinal;
}
注意这个self.getClass() != other.getClass() && self.getDeclaringClass() != other.getDeclaringClass()
在这里你一定会问,为什么还要进行getDeclaringClass()的比较,直接比较两个枚举常量的class对象类型是否相同不就行了吗?
观察如下的实例:
在使用枚举类的时候,建议用getDeclaringClass返回枚举类。但是为什么不用getClass呢?下面来看看代码:
public enum FruitEnum{ BANANA,APPLE; public static void main(String[] args) { System.out.println(BANANA.getDeclaringClass()); System.out.println(BANANA.getClass()); } } # 运行结果 class FruitEnum class FruitEnum }
有人说结果不是一样吗?不急,看下面这种情况。
public enum FruitEnum{ BANANA{ String getName() { return "香蕉"; } },APPLE{ String getName() { return "苹果"; } }; abstract String getName(); public static void main(String[] args) { System.out.println(BANANA.getDeclaringClass()); System.out.println(BANANA.getClass()); } } # 运行结果 class FruitEnum class FruitEnum$1
这种情况下就不同了。因为此时BANANA和APPLE相当于FruitEnum的内部类。下面来看看Enum的源码:
public final Class<E> getDeclaringClass() { Class var1 = this.getClass(); Class var2 = var1.getSuperclass(); // 获取上一级的父类 return var2 == Enum.class?var1:var2; }
当上一级的父类不是Enum,则返回上一级的class。因此对枚举类进行比较的时候,使用getDeclaringClass是万无一失的。
————————————————
版权声明:本文为CSDN博主「DaJian35」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/DaJian35/article/details/79705193
再来看getDeclaringClass方法:
/**返回与该enum常量的enum类型对应的类对象。当且仅当e1.getingclass () == e2.getingclass()时,两个enum常量e1和e2具有相同的enum类型。(此方法返回的值可能与对象返回的值不同。使用特定于常量的类主体的枚举常量的getClass方法。)
* Returns the Class object corresponding to this enum constant's
* enum type. Two enum constants e1 and e2 are of the
* same enum type if and only if
* e1.getDeclaringClass() == e2.getDeclaringClass().
* (The value returned by this method may differ from the one returned
* by the {@link Object#getClass} method for enum constants with
* constant-specific class bodies.)
*
* @return the Class object corresponding to this enum constant's enum type 对应于enum常量的类对象枚举类型
*/
@SuppressWarnings("unchecked")
public final Class<E> getDeclaringClass() {
Class<?> clazz = getClass();
Class<?> zuper = clazz.getSuperclass();
return (zuper == Enum.class) ? (Class<E>)clazz : (Class<E>)zuper;
}
所以当枚举常量是普通的常量时,“clazz.getSuperclass()”获得得到的是“Enum.class”,此时返回的是就是这个枚举常量对应的Class实例。但是若该枚举常量是以匿名内部类的形式出现,则“clazz.getSuperclass()”返回的就是该枚举常量对应的Class实例,则返回的是“clazz.getSuperclass()”的计算结果。
参考链接:
如果您觉得阅读本文对您有帮助,请点一下“推荐”按钮,您的“推荐”将是我最大的写作动力!欢迎各位转载,但是未经作者本人同意,转载文章之后必须在文章页面明显位置给出作者和原文连接,否则保留追究法律责任的权利。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· Vue3状态管理终极指南:Pinia保姆级教程