从初学者到专家:Java枚举的完整指南

素颜马尾好姑娘i 2024-04-26 01:41 143阅读 0赞

645eeffa534a4a1c8eed18ed3807796b.png

" class="reference-link">3d4d68c5deda41e5ad04f480e29c45a5.png

#

1.枚举的概念

在Java中,枚举是一种特殊的数据类型,用于定义一组有限的命名常量。枚举提供了一种更直观、更可读的方式来表示一组相关的常量,并且可以为这些常量绑定其他数据或行为。

背景:枚举是在JDK1.5以后引入的。

主要用途是:将一组常量组织起来,在这之前表示一组常量通常使用定义常量的方式

要定义一个枚举,需要使用enum关键字。以下是一个简单的枚举定义的示例:

创建一个枚举类:TestEnum

7aed3855cc3a4e999e85c85229d5833e.png

  1. public enum TestEnum {
  2. RED, // 枚举常量 RED
  3. BLACK, // 枚举常量 BLACK
  4. GREEN, // 枚举常量 GREEN
  5. WHITE; // 枚举常量 WHITE
  6. public static void main(String[] args) {
  7. TestEnum testEnum2 = TestEnum.BLACK; // 创建一个变量 testEnum2 并将其设置为 BLACK 枚举常量
  8. switch (testEnum2) { // 使用 switch 语句根据 testEnum2 的值进行判断
  9. case RED:
  10. System.out.println("red"); // 如果 testEnum2 的值为 RED,则输出 "red"
  11. break;
  12. case BLACK:
  13. System.out.println("black"); // 如果 testEnum2 的值为 BLACK,则输出 "black"
  14. break;
  15. case WHITE:
  16. System.out.println("WHITE"); // 如果 testEnum2 的值为 WHITE,则输出 "WHITE"
  17. break;
  18. case GREEN:
  19. System.out.println("green"); // 如果 testEnum2 的值为 GREEN,则输出 "green"
  20. break;
  21. default:
  22. break;
  23. }
  24. }
  25. }

2.枚举的常用方法

Enum类是所有枚举类型的基类,在Java中提供了一些常用的方法来操作和处理枚举类型。下面是

Enum类的常用方法:

cae1a58ff66240bb9476130956fbb963.png

1.values ()方法:

  1. package demo2;
  2. public enum TestEnum {
  3. RED, // 枚举常量 RED
  4. BLACK, // 枚举常量 BLACK
  5. GREEN, // 枚举常量 GREEN
  6. WHITE; // 枚举常量 WHITE
  7. public static void main(String[] args) {
  8. TestEnum[] values = TestEnum.values();
  9. for (TestEnum value : values) {
  10. System.out.println("Name: " + value.name() + ", Index: " + value.ordinal());
  11. }
  12. }
  13. }

运行截图:

89e7904667444f5fb245c3675f4eafec.png


valueOf(String name)方法:

  1. package demo2;
  2. public enum TestEnum {
  3. RED, // 枚举常量 RED
  4. BLACK, // 枚举常量 BLACK
  5. GREEN, // 枚举常量 GREEN
  6. WHITE; // 枚举常量 WHITE
  7. public static void main(String[] args) {
  8. String colorName = "GREEN";
  9. TestEnum enumInstance = TestEnum.valueOf(colorName);
  10. System.out.println("Enum Constant Name: " + enumInstance.name());
  11. }
  12. }

运行截图:

ab42d0e623c143e39b16dbcebdf7034f.png


3.ordinal()方法:

  1. package demo2;
  2. public enum TestEnum {
  3. RED, // 枚举常量 RED
  4. BLACK, // 枚举常量 BLACK
  5. GREEN, // 枚举常量 GREEN
  6. WHITE; // 枚举常量 WHITE
  7. public static void main(String[] args) {
  8. TestEnum testEnum2 = TestEnum.BLACK; // 创建一个变量 testEnum2 并将其设置为 BLACK 枚举常量
  9. int index = testEnum2.ordinal(); // 使用 ordinal() 方法获取 testEnum2 的索引位置
  10. System.out.println("Index: " + index);
  11. }
  12. }

运行截图:

710fa61d625d494b823b9bdf3a1aa609.png


4.compareTo()方法:

  1. public enum TestEnum {
  2. RED, // 枚举常量 RED
  3. BLACK, // 枚举常量 BLACK
  4. GREEN, // 枚举常量 GREEN
  5. WHITE; // 枚举常量 WHITE
  6. public static void main(String[] args) {
  7. TestEnum testEnum1 = TestEnum.RED; // 创建一个变量 testEnum1 并将其设置为 RED 枚举常量
  8. TestEnum testEnum2 = TestEnum.BLACK; // 创建一个变量 testEnum2 并将其设置为 BLACK 枚举常量
  9. int comparisonResult = testEnum1.compareTo(testEnum2); // 使用 compareTo() 方法比较 testEnum1 和 testEnum2 的顺序
  10. if (comparisonResult < 0) { // 如果 comparisonResult 小于 0
  11. System.out.println(testEnum1 + " comes before " + testEnum2); // 输出 testEnum1 在 testEnum2 之前
  12. } else if (comparisonResult > 0) { // 如果 comparisonResult 大于 0
  13. System.out.println(testEnum1 + " comes after " + testEnum2); // 输出 testEnum1 在 testEnum2 之后
  14. } else {
  15. System.out.println(testEnum1 + " and " + testEnum2 + " are the same"); // 输出 testEnum1 和 testEnum2 相同
  16. }
  17. }
  18. }

运行截图:

314a4f20f3974e23ad7a8c31bd95264e.png


3.枚举的构造方法

枚举类型在Java中可以包含构造方法,用于初始化枚举常量。枚举的构造方法是私有的,因为枚举常量在定义时就被实例化,并且不能在其他地方进行实例化。

同时:当枚举对象有参数后,需要提供相应的构造函数

代码案例1:

  1. public enum TestEnum {
  2. RED("Red Color"), // 枚举常量 RED
  3. BLACK("Black Color"), // 枚举常量 BLACK
  4. GREEN("Green Color"), // 枚举常量 GREEN
  5. WHITE("White Color"); // 枚举常量 WHITE
  6. private String color; // 枚举常量的属性
  7. private TestEnum(String color) {
  8. this.color = color; // 枚举常量的构造方法
  9. }
  10. public String getColor() {
  11. return color; // 获取枚举常量的颜色属性
  12. }
  13. }

在上述代码中,TestEnum枚举类型包含了一个构造方法,它接受一个color参数,并将其赋值给枚举常量的color属性。构造方法是私有的,只能在枚举类型内部使用。

代码案例2:

  1. public enum TestEnum {
  2. RED("Red Color", 1), // 枚举常量 RED,具有颜色属性为 "Red Color" 和代码属性为 1
  3. BLACK("Black Color", 2), // 枚举常量 BLACK,具有颜色属性为 "Black Color" 和代码属性为 2
  4. GREEN("Green Color", 3), // 枚举常量 GREEN,具有颜色属性为 "Green Color" 和代码属性为 3
  5. WHITE("White Color", 4); // 枚举常量 WHITE,具有颜色属性为 "White Color" 和代码属性为 4
  6. private String color; // 枚举常量的颜色属性
  7. private int code; // 枚举常量的代码属性
  8. private TestEnum(String color, int code) {
  9. this.color = color; // 构造方法,用于初始化枚举常量的颜色属性和代码属性
  10. this.code = code;
  11. }
  12. public String getColor() {
  13. return color; // 获取枚举常量的颜色属性
  14. }
  15. public int getCode() {
  16. return code; // 获取枚举常量的代码属性
  17. }
  18. }

在上述代码中,TestEnum枚举类型包含了一个接受两个参数的构造方法。每个枚举常量都会调用该构造方法进行初始化,并传递对应的参数。

除了构造方法,我们在枚举类型中定义了getColor()getCode()方法,用于获取枚举常量的颜色属性和代码属性。

代码案例3:

  1. public enum TestEnum {
  2. RED("Red Color", 1, true), // 枚举常量 RED
  3. BLACK("Black Color", 2, false), // 枚举常量 BLACK
  4. GREEN("Green Color", 3, true), // 枚举常量 GREEN
  5. WHITE("White Color", 4, false); // 枚举常量 WHITE
  6. private String color; // 枚举常量的颜色属性
  7. private int code; // 枚举常量的代码属性
  8. private boolean active; // 枚举常量的活动状态属性
  9. private TestEnum(String color, int code, boolean active) {
  10. this.color = color;
  11. this.code = code;
  12. this.active = active;
  13. }
  14. public String getColor() {
  15. return color;
  16. }
  17. public int getCode() {
  18. return code;
  19. }
  20. public boolean isActive() {
  21. return active;
  22. }
  23. }

在上述示例中,我们为枚举常量定义了一个带有三个参数的构造方法。每个枚举常量都会调用该构造方法进行初始化,并传递相应的参数。

除了构造方法,我们还定义了getColor()getCode()isActive()方法,用于获取枚举常量的颜色属性、代码属性和活动状态属性。

4.枚举和反射

我们知道反射( 从初学者到专家:Java反射的完整指南-CSDN博客 )是,对于任何一个类,哪怕其构造方法是私有的,我们也可以通过反射拿到他的实例对象,那么枚举的构造方法也是私有的,我们是否可以拿到呢?

  1. package demo2;
  2. import java.lang.reflect.Constructor;
  3. public enum TestEnum {
  4. RED("red",1),BLACK("black",2),WHITE("white",3),GREEN("green",4);
  5. private String name;
  6. private int key;
  7. private TestEnum (String name,int key) {
  8. this.name = name;
  9. this.key = key;
  10. }
  11. public static TestEnum getEnumKey (int key) {
  12. for (TestEnum t: TestEnum.values()) {
  13. if(t.key == key) {
  14. return t;
  15. }
  16. }
  17. return null;
  18. }
  19. public static void reflectPrivateConstructor() {
  20. try {
  21. Class<?> classStudent = Class.forName("TestEnum");
  22. //注意传入对应的参数,获得对应的构造方法来构造对象,当前枚举类是提供了两个参数分别是String和int。
  23. Constructor<?> declaredConstructorStudent = classStudent.getDeclaredConstructor(String.class,int.class);
  24. //设置为true后可修改访问权限
  25. declaredConstructorStudent.setAccessible(true);
  26. Object objectStudent = declaredConstructorStudent.newInstance("绿色",666);
  27. TestEnum testEnum = (TestEnum) objectStudent;
  28. System.out.println("获得枚举的私有构造函数:"+testEnum);
  29. } catch (Exception ex) {
  30. ex.printStackTrace();
  31. }
  32. }
  33. public static void main(String[] args) {
  34. reflectPrivateConstructor();
  35. }
  36. }

运行截图:

e979ea50e4304b729232b9ca5da4c920.png

这里的主要异常是: 就是没有对应的构造方法!

为什么呢?

因为我们提供的枚举的构造方法就是两个参数分别是 String 和 int,我们所有的枚举类,都是默认继承与 java.lang.Enum ,说到继承,就要帮助父类进行构造!而我们写的类,并没有帮助父类构造!那意思是,我们要在自己的枚举类里面,提供super 吗?不是的,枚举比较特殊,虽然我们写的是两个,但是默认他还添加了两个参数,哪两个参数呢?

我们看一下Enum 类的源码

d5a69419729c42439cf371772d8ae2a6.png

也就是说,我们自己的构造函数有两个参数一个是String一个是int,同时他默认后边还会给两个参数,一个是String一个是int。也就是说,这里我们正确给的是4个参数:

代码:

  1. package demo2;
  2. import java.lang.reflect.Constructor;
  3. public enum TestEnum {
  4. RED("red",1),
  5. BLACK("black",2),
  6. WHITE("white",3),
  7. GREEN("green",4);
  8. private String name;
  9. private int key;
  10. private TestEnum (String name,int key) {
  11. this.name = name;
  12. this.key = key;
  13. }
  14. public static TestEnum getEnumKey (int key) {
  15. for (TestEnum t: TestEnum.values()) {
  16. if(t.key == key) {
  17. return t;
  18. }
  19. }
  20. return null;
  21. }
  22. public static void reflectPrivateConstructor() {
  23. try {
  24. Class<?> classStudent = Class.forName("demo2.TestEnum");
  25. //注意传入对应的参数,获得对应的构造方法来构造对象,当前枚举类是提供了两个参数分别是String和int。
  26. Constructor<?> declaredConstructorStudent =
  27. classStudent.getDeclaredConstructor(String.class,int.class,String.class,int.class);
  28. //设置为true后可修改访问权限
  29. declaredConstructorStudent.setAccessible(true);
  30. //后两个为子类参数,大家可以将当前枚举类的key类型改为double验证
  31. Object objectStudent = declaredConstructorStudent.newInstance("父类参数",666,"子类参数",888);
  32. TestEnum testEnum = (TestEnum) objectStudent;
  33. System.out.println("获得枚举的私有构造函数:"+testEnum);
  34. } catch (Exception ex) {
  35. ex.printStackTrace();
  36. }
  37. }
  38. public static void main(String[] args) {
  39. reflectPrivateConstructor();
  40. }
  41. }

运行截图:

bb65f20f134541cd89db605a4e2a0c57.png

但还是报错了,为什么?

直接公布答案!

c4d97485cfb448399769aa021a7eb9e3.png

根据Java源代码, newInstance() 方法在创建实例之前会先检查目标类是否是一个枚举类型。如果目标类是枚举类型, newInstance() 方法会抛出 InstantiationException 异常,阻止通过反射去创建枚举实例。

有一道面试题就是关于: 为什么枚举实现单例模式是安全的?

有兴趣的小伙伴可以去了解一下


5.总结

以下是关于枚举的一些总结:

  1. 定义枚举:可以使用关键字‘enum’来定义枚举类型。枚举类型的定义通常位于类的顶层,可以包含枚举常量、字段、方法等。
  2. 枚举常量:枚举类型中的常量称为枚举常量。它们是枚举类型的实例,使用预定义的名称来表示特定的常量值。枚举常量在枚举类型的定义中以逗号分隔,并以大写字母命名。
  3. 枚举方法:枚举可以包含方法,可以为枚举类型添加自定义的行为。枚举方法可以在每个枚举常量上调用,并可以在枚举类型内部定义。
  4. 枚举的比较:枚举类型可以使用`==`运算符进行比较,因为每个枚举常量都是唯一的。
  5. 枚举的序列化:枚举类型默认是可序列化的,可以直接将枚举类型的对象进行序列化和反序列化操作。
  6. 枚举的限制:枚举常量在编译时就被确定,无法在运行时动态创建新的枚举常量。枚举常量的数量是固定的。
  7. 枚举的特性

    • 唯一性:枚举常量是唯一的,每个枚举常量在枚举类型中只会存在一个实例。
    • 不可变性:枚举常量是不可变的,一旦创建,其值无法修改。
    • 安全性:枚举常量在多线程环境下是安全的,不需要额外的同步措施。
    • 可迭代性:枚举类型可以使用`values()`方法获取包含所有枚举常量的数组,并支持使用增强的`for-each`循环进行遍历。

优点:

  1. 类型安全性:枚举提供了类型安全性,编译器可以在编译时检查枚举类型的正确使用。枚举常量只能是预定义的值,不允许其他值的赋值,从而减少了错误的发生。

  2. 可读性和可维护性:枚举常量使用预定义的名称来表示特定的常量值,这提供了更好的代码可读性。使用枚举可以使代码更加清晰、易于理解和维护。

  3. 易于扩展:在需要添加新的常量时,可以简单地在枚举中定义新的枚举常量。这样可以方便地扩展现有的枚举类型,而不会影响到其他部分的代码。

  4. 单例模式的简化:枚举本身就是单例模式的一种实现方式。枚举常量是唯一的,且在多线程环境下是安全的,不需要额外的同步措施。

缺点:

1. 限制了灵活性:枚举常量的数量是固定的,它们在编译时就被确定,无法在运行时动态创建新的枚举常量。这种限制可能会导致在某些特定的场景下,无法灵活地扩展枚举类型。

  1. 不适合表示连续变化的值:枚举适用于表示一组固定的离散的常量值,但不适合表示连续变化的值。如果需要表示一系列连续变化的值,使用枚举可能会显得笨拙和不合适。

  2. 可序列化的复杂性:枚举类型默认是可序列化的,但在某些情况下,当枚举类型需要进行序列化和反序列化时,可能会引起一些复杂性和不一致性的问题。

总体而言,枚举在许多情况下都是一种有用的工具,可以提供类型安全性、可读性和可维护性。然而,在一些特定的场景下,枚举的限制可能会导致不适合使用枚举,需要考虑其他的解决方案。

885961cdd3994a73b5425249f2f645a6.png

发表评论

表情:
评论列表 (有 0 条评论,143人围观)

还没有评论,来说两句吧...

相关阅读