目录
枚举是什么
常用方法
构造方法
枚举的优缺点
枚举和反射
实现单例模式
枚举是什么
枚举(enum):是一种特殊的类,用于定义一组常量,将其组织起来。枚举使得代码更具有可读性和可维护性,特别是在处理固定集合的值时,如:星期、月份、状态码等
在 Java 中,使用关键字 enum 来定义枚举类:
public enum Day { SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY;}
其中,定义的枚举项就是该类的实例,且必须在第一行,最后一个枚举项后的分号; 可以省略,但是若枚举类有其他内容,则分号不能省略(最好不要省略)
当类初始化时,这些枚举项就会被实例化
枚举类使用 enum 定义后,默认继承 java.lang.Enum 类,也就是说,我们自己写的枚举类,就算没有显示的继承 Enum,但是其默认继承了这个类
此外,枚举在 Java 中不能被继承,自定义的枚举类隐式继承自 java.lang.Enum 类,且不能再继承其他类,这样的设计确保了枚举类的简单性和一致性。如果枚举可以继承其他类,将会导致复杂的继承关系,并且影响Java的类型系统
常用方法
方法 | 描述 |
---|---|
values() | 以数组的形式返回枚举类型的所有成员 |
ordinal() | 获取枚举成员的索引位置 |
valueOf() | 将普通字符串转换为枚举实例 |
compareTo(E o) | 比较两个枚举成员在定义时的顺序 |
我们通过一个示例,来学习和使用这些方法:
public enum Day { SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY; public static void main(String[] args) { // 获取所有枚举成员 Day[] days = Day.values(); // 遍历 for (int i = 0; i < days.length; i++) { // 获取枚举成员以及索引位置 System.out.println(days[i] + " " + days[i].ordinal()); } // 将普通字符串转换为枚举实例 System.out.println(Day.valueOf("THURSDAY")); // 获取枚举实例 SUNDAY 和 SATURDAY Day sunday = Day.SUNDAY; Day saturday = Day.SATURDAY; // 比较定义时的顺序 System.out.println(sunday.compareTo(saturday)); }}
运行结果:
在使用 valueOf() 方法进行转换时,传递的名称必须与枚举常量的名字完全匹配(包括大小写),若不匹配,就会抛出 IllegalArgumentException 异常:
当我们查看 java.lang.Enum 时:
可以看到,valueOf() 方法包含了两个类型的参数:Class<T> enumClass 和 String name
其中
Class<T> enumType 是一个 Class 对象,表示要查找的枚举类型
String name: 是一个字符串,表示要查找的枚举常量的名称。名称必须与枚举常量的名字完全匹配(包括大小写)
但是在使用时,我们只传递了一个参数 name,也能够进行转换,这是为什么呢?
这是因为,在 Java 中,valueOf 方法实际上是自动生成的,属于每个枚举类型的特性。虽然它的原始定义需要两个参数(类类型和名称),但是每个枚举类型都会自动提供一个与自身类型相关联的 valueOf 方法,只需传递一个字符串参数
因此,当我们调用 Day.valueOf("THURSDAY") 时,Java会自动处理这个调用,实际调用的是包含类名的 valueOf 方法,而不是原始的静态方法定义
再观察 java.lang.Enum:
我们会发现,其中并不存在 values() 这个方法,而当我们点击 values() 方法时,则会跳转到本类上
那么,values() 方法是从哪来的呢?
values() 方法是枚举类自动提供的一个静态方法,允许我们获取一个包含所有枚举常量的数组,这个方法是由Java编译器自动生成的,在编译时每个枚举类型都会自动生成一个 values() 方法,因此不需要我们显式定义它
我们将枚举类进行反编译:
(1)打开 cmd,切换到 Day.java 文件所在目录
(2)编译 .java 文件(javac Day.java)
(3)将 .class 文件进行反编译(javap -c Day.class > day.txt)
打开 day.txt,可以看到:
编译器自动为我们生成了 values 和 valueOf 方法
构造方法
当我们创建构造方法时:
不能使用 public 来修饰构造方法,为什么呢?
这是因为,在 Java 中,枚举类的构造方法都是私有的(不加任何修饰符时,默认是 private),无法在枚举类外部调用,这也就防止了在枚举类外部创建新的枚举常量
在定义枚举常量时,构造方法会被隐式调用:
public enum Day { SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY; Day() { System.out.println("构造方法"); } public static void main(String[] args) { System.out.println("------------------"); }}
运行结果:
当我们定义带有参数的构造方法时,在创建枚举项时,也要为其提供对应参数:
public enum Day { SUNDAY("周天", 7), MONDAY("周一", 1), TUESDAY("周二", 2), WEDNESDAY("周三", 3), THURSDAY("周四", 4), FRIDAY("周五", 5), SATURDAY("周六", 6); private String name; private int key; Day(String name, int key) { this.name = name; this.key = key; }}
枚举的优缺点
优点:
(1)枚举常量确保了只能使用定义的常量,避免了使用整型常量时可能带来的错误
(2)使用枚举可以使代码更易读,表达清晰
(3)枚举定义了一组固定的常量,适合表示有限的状态或选项,便于管理和维护
(4)枚举可以拥有字段、方法和构造方法,能够封装与常量相关的行为和属性
(5)Java的枚举类自带一些方法,如 values()、valueOf() 等
(6)可以用于 switch 语句
缺点:
(1)枚举的集合是固定的,无法在运行时添加或删除常量。如果需要动态的集合,枚举可能不适用
(2)枚举不能继承,无法扩展
(3)每个枚举常量都是一个对象,可能会增加内存使用,特别是当枚举常量数量较多时
枚举和反射
在 Java 反射-CSDN博客 中,我们学习了反射,通过反射,我们可以拿到类的私有构造方法,从而创建实例对象
那么,枚举是否可以通过反射,拿到实例对象呢?
public enum Day { SUNDAY("周天", 7), MONDAY("周一", 1), TUESDAY("周二", 2), WEDNESDAY("周三", 3), THURSDAY("周四", 4), FRIDAY("周五", 5), SATURDAY("周六", 6); private String name; private int key; Day(String name, int key) { this.name = name; this.key = key; }}
public class Test { public static void main(String[] args) { Class<?> classDay = null; try { classDay = Class.forName("Day"); Constructor<?> constructor = classDay.getDeclaredConstructor(String.class, int.class); constructor.setAccessible(true); Day day = (Day) constructor.newInstance("sunday", 0); System.out.println(day.ordinal()); } catch (ClassNotFoundException e) { throw new RuntimeException(e); } catch (NoSuchMethodException e) { throw new RuntimeException(e); } catch (InvocationTargetException e) { throw new RuntimeException(e); } catch (InstantiationException e) { throw new RuntimeException(e); } catch (IllegalAccessException e) { throw new RuntimeException(e); } }}
运行结果:
此时程序抛出了 NoSuchMethodException 异常,也就是没有对应的构造方法
但是提供的枚举的构造方法就是带有两个参数,分别为 String 和 int:
那么,问题出在哪里呢?
自定义的枚举类默认继承自 java.lang.Enum
因此,自定义的枚举类继承了父类除构造方法外的所有东西,且子类需要帮助父类进行构造,
但我们实现的类中,并没有帮助父类进行构造
因此,我们需要在枚举类中帮助父类进行构造,而父类中的构造方法为:
那么,如何实现呢?通过 super 方法吗?
但是,当我们在构造方法中调用 super 时:
枚举构造方法中不能使用 super
由于枚举比较特殊,在构造方法中,除了我们自定义了两个参数,它还默认添加了父类的两个参数
也就是说,构造函数中一共有四个参数:String int String int
其中,前两个参数是父类参数,后两个参数是子类参数
public class Test { public static void main(String[] args) { Class<?> classDay = null; try { classDay = Class.forName("enumDemo.Day"); Constructor<?> constructor = classDay.getDeclaredConstructor(String.class, int.class, String.class, int.class); constructor.setAccessible(true); Day day = (Day) constructor.newInstance("父类参数", 0, "子类参数", 0); System.out.println(day.ordinal()); } catch (ClassNotFoundException e) { throw new RuntimeException(e); } catch (NoSuchMethodException e) { throw new RuntimeException(e); } catch (InvocationTargetException e) { throw new RuntimeException(e); } catch (InstantiationException e) { throw new RuntimeException(e); } catch (IllegalAccessException e) { throw new RuntimeException(e); } }}
再次运行:
此时抛出了 IllegalArgumentException 异常,不能通过反射创建枚举对象
枚举保证了每个枚举常量只有一个实例,这种唯一性在枚举类型被定义时就已经确定,不允许外部创建新的实例,枚举类型的设计使得它们的实例在类加载时被唯一地定义,从而避免了通过反射创建新的枚举实例的可能性,确保了枚举的强类型安全性和唯一性
实现单例模式
在 单例模式:饿汉模式、懒汉模式_单例模式懒汉和饿汉-CSDN博客 中我们实现了单例模式,单例模式能够确保一个类只有一个实例
但普通类可以通过反射机制打破,因此,我们可以使用枚举来实现单例模式
public enum Singleton { INSTANCE; public Singleton getInstance() { return INSTANCE; }}