java枚举类与成员变量的关系_深入理解枚举类_蔻蔻42的博客-程序员秘密

技术标签: java枚举类与成员变量的关系  

深入理解枚举

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

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

1. 枚举类的继承结构:

1b14b9e8843b9ee74f699475058ffc23.png

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

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."":(Ljava/lang/String;I)V

3: invokespecial #6 // Method java/lang/Enum."":(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 "":(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 "":(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 "":(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 "":(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;

SourceFile: "Demo69.java"

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

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

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

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

4d59f8b99c57ce458bf37eed00ae9aa9.png

引用自“疯狂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都是表示匿名内部类。

8acb206a112f74b3bbeb0a470367fab5.png

c3df96b45861689c1448cfbd98ecf77b.png

注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 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 {

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 "":(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()方法

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 aClass = SingletonClass.class;

Constructor constructor = aClass.getDeclaredConstructor(null);

constructor.setAccessible(true);

constructor.newInstance();

} catch (Exception e) {

e.printStackTrace();

}

}

}

运行结果:

INSTANCE

java.lang.NoSuchMethodException: com.bigdata.java.SingletonClass.()

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.()”方法异常?

为了解答这个问题,我们需要对该枚举类执行反编译,在反编译后能够看到这样一个构造方法“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."":(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."":(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 "":(Ljava/lang/String;I)V

//调用该类的构造方法"":(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 "":(Ljava/lang/String;I)V 方法。再回到上面的问题中,由于没有生成无参的构造方法,只有这么一个构造方法,所以会给出“java.lang.NoSuchMethodException: com.bigdata.java.SingletonClass.()”异常信息。

既然没有无参构造方法,那么我们能否通过调用Method "":(Ljava/lang/String;I)V 构造方法来进行实例化呢?

如果这样来修改main方法

public static void main(String[] args) {

System.out.println(SingletonClass.INSTANCE);

try {

Class aClass = SingletonClass.class;

Constructor 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>

implements Comparable, 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 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 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 getDeclaringClass() {

Class> clazz = getClass();

Class> zuper = clazz.getSuperclass();

return (zuper == Enum.class) ? (Class)clazz : (Class)zuper;

}

所以当枚举常量是普通的常量时,“clazz.getSuperclass()”获得得到的是“Enum.class”,此时返回的是就是这个枚举常量对应的Class实例。但是若该枚举常量是以匿名内部类的形式出现,则“clazz.getSuperclass()”返回的就是该枚举常量对应的Class实例,则返回的是“clazz.getSuperclass()”的计算结果。

参考链接:

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_33691489/article/details/114774913

智能推荐

安装anaconda配置pytorch虚拟环境遇到的问题及解决办法_风吹落叶花飘荡的博客-程序员秘密

安装anaconda配置pytorch虚拟环境遇到的问题及解决办法PackagesNotFoundError: The following packages are not available from current channels:python-3.8Current channels:https://repo.anaconda.com/pkgs/main/win-64https://repo.anaconda.com/pkgs/main/noarchhttps://repo.anacond

cifar-100 数据集转图片 python 代码_我才是一卓的博客-程序员秘密

注:最终图片数据集以 100 分类文件夹存放。from PIL import Imageimport numpy as npimport pickleimport osfrom tqdm import trangefrom os.path import joindef my_mkdirs(path): if not os.path.exists(path): os.makedirs(path)def unpickle(file): with op

金蝶EAS,打开新页面,传递参数,KDTable表格数据填充_公子旭的博客-程序员秘密

业务场景:打开新页面,并在新页面填充数据。可以在新页面的onLoad方法中获取前一页面提供的参数,然后解析当前页面的表格,填充数据。/** * 加载界面时,填充拆分明细数据 */@Overridepublic void onLoad() throws Exception { super.onLoad(); setUITitle("拆分明细"); SplitBillInfo

安卓实习第十八天_精致的狗狗女孩的博客-程序员秘密

- 进入页面不让Edittext自动获取焦点找一个EditText的父级控件,将其设置成android:focusable="true" android:focusableInTouchMode="true"- Toast工具类可及时更新Toast内容import android.content.Context;import android.widget.Toast;public class

『 论文阅读』10 CHALLENGING PROBLEMS IN DATA MINING RESEARCH_百川AI的博客-程序员秘密

很多不错论文都引用了此篇论文,于是阅读了这篇06年论文。Abstract介绍数据挖掘中的10个具有挑战性的问题,分析数据挖掘问题出现位置的一份高级指南。 这篇文章是作者通过咨询一些最活跃的数据挖掘和机器学习研究人员(IEEE ICDM和ACM KDD会议的组织者),就他们对未来数据挖掘研究的重要和有价值的主题发表的意见。1. Developing a Unifying T...

书评:Java核心编程卷1——基础_普通网友的博客-程序员秘密

本文来源于我在InfoQ中文站翻译的文章,原文地址是:http://www.infoq.com/cn/articles/core-java-fundamentals现在已经步入了21世纪,我们很难想象曾经Java相关的图书是那样的稀少,但这就是Java刚出现时的情况。那时所有与Java相关的图书基本上都是由Sun公司的几个工程师们所发布的,比如说Arthur Van Hoff et.al所写的“H

随便推点

horizo虚拟机_适用于所有技能水平的世界一流的工具和编辑器扩展:与Black Horizo​​n Studios见面_culiao6493的博客-程序员秘密

horizo虚拟机For Black Horizon Studios, succeeding as a Unity Asset Store publisher has meant striking a balance between being accessible enough for hobbyists and flexible enough for pros. By striving to ...

js 数字金额转汉字_js 数字金额 转中文金额_cuiyiaa的博客-程序员秘密

1.html页面 文件名:index.html<!DOCTYPE html><html> <head> <meta charset="utf-8" /> <title></title> <script src="js/numbig.js" type="text/javascript" charset="utf-8"></script>

lle算法的matlab实现,lle算法详解及matlab代码实现_华清阙的博客-程序员秘密

LLE算法代码% LLE ALGORITHM (using K nearest neighbors)%% [Y] = lle(X,K,dmax)%% X = data as D x N matrix (D = dimensionality, N = #points)%(D = 点的维数, N = 点数)% K = number of neighbors(领域点的个数)% dmax = max em...

spoj 10606 数位dp统计_Andy20141210的博客-程序员秘密

题目意思:要求数字偶数数位出现基数次,基数数位出现偶数次,不出现,不违反规则。状态压缩:3进制数 0表示未出现过 1表示出现基数次 2表示出现偶数次 1 #include&lt;iostream&gt; 2 #include&lt;cstring&gt; 3 #include&lt;cstdlib&gt; 4 #include&lt;cstdio&gt;...

美赛是如何判定作弊的?_数学建模BOOM的博客-程序员秘密

2021前3题的评奖结果,可以看到26个队未成功参赛(Unsuccessful Participant),204个队被取消评奖资格(Disqualified),11个队不予评奖(Not Judged)。

基于node的前端项目编译时内存溢出问题_weixin_34228662的博客-程序员秘密

前段时间公司有个基于vue的项目在运行npm run build的时候会报内存溢出,今天在某个技术流交群也有位小伙伴基于angular的项目也出现了这个问题,所以查了一些相关的资料总结了一下,下面会详细说明前端三大框架编译时遇到这个问题具体怎么解决。首先看我模拟出的报错内容具体截图如下里面有句关键的话,CALL_AND_RETRY_...

推荐文章

热门文章

相关标签