一、初始枚举
Java 枚举(Enum)是一种特殊的类,用于定义一组相关的常量。Java 枚举是一种特殊的类,用于定义一组相关的常量。枚举在Java中提供了很多便利的功能,比如天然的单例模式实现,线程安全性,以及在switch语句中的强大支持。
-
定义枚举
public enum Color { RED, GREEN, BLUE; }
在这个例子中,
Color
是一个枚举类型,它包含了三个常量:RED
,GREEN
,BLUE
。 -
枚举实例化 枚举类型不是通过构造函数创建对象的,它们由JVM自动创建,并在需要时提供给代码。因此,你可以直接通过枚举类型和常量名称来引用它们,比如
Color.RED
。 -
枚举方法: 枚举类型可以定义方法,这些方法可以由枚举常量调用。
public enum DayOfWeek { MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY; public boolean isWeekday() { return this != SATURDAY && this != SUNDAY; } }
在这个例子中,
DayOfWeek
枚举有一个isWeekday
方法。具体调用示例如下:public static void main( String[] args ){ if (DayOfWeek.MONDAY.isWeekday()) { //业务代码 System.out.println( "Hello World!" ); } }
- 枚举构造器与匿名内部类: 枚举可以有构造函数,并且可以使用匿名内部类为每个枚举常量添加行为。
public enum Shape { CIRCLE(0), SQUARE(1); private int code; Shape(int code) { this.code = code; } public int getCode() { return code; } }
CIRCLE
和SQUARE
有自己的code
值。 -
枚举的继承特性 枚举默认继承自
java.lang.Enum
类,这意味着它们可以有自己的方法和字段。 -
枚举数组 你可以通过
values()
方法获取枚举的所有实例,形成一个数组:Color[] colors = Color.values();
-
switch语句与枚举 枚举非常适合在
switch
语句中使用,提供了更安全和可读的代码:switch (color) { case RED: System.out.println("Red color"); break; case GREEN: System.out.println("Green color"); break; // ... }
-
枚举作为单例模式 枚举是天然的单例,因为它们在类加载时就被初始化,并且不允许外界实例化。
为什么枚举在多线程环境中是线程安全的?
枚举在Java中是隐式静态final的,这意味着它们在编译时就被初始化,并且在运行时被视为不可变对象。由于它们是在类加载时就创建的,所以在多线程环境中,它们自然就是线程安全的,不需要额外的同步措施。
二、用代码详细说明枚举在多线程中的使用
2.1 举例一
在多线程环境中,枚举通常用于创建线程安全的全局变量或者实现特定的并发策略。以下是一个简单的例子,展示如何使用枚举来实现一个线程安全的计数器:
public enum ThreadSafeCounter {
INSTANCE;
private int count = 0;
public synchronized void increment() {
count++;
}
public synchronized int getCount() {
return count;
}
}
在这个示例中,ThreadSafeCounter
是一个枚举,只有一个名为 INSTANCE
的实例。枚举实例是天然线程安全的,因为它们在JVM启动时被初始化,并且不能通过反射或其他方式创建新的实例。increment()
和 getCount()
方法都添加了 synchronized
关键字,以确保在多线程环境下访问这些方法时的安全性。
2.2 举例二
在多线程环境中,枚举通常用于表示一组固定的常量,这些常量在程序运行期间不会发生变化,因此不需要特别处理线程安全问题。下面是一个简单的例子,展示了如何在一个枚举中定义一个在多线程环境下使用的状态机:
public enum State {
ACTIVE,
INACTIVE,
TERMINATED;
// 假设我们有一个需要在多线程环境下的操作方法
public void performAction() {
switch (this) {
case ACTIVE:
System.out.println("Performing action in active state");
break;
case INACTIVE:
System.out.println("Action not allowed in inactive state");
break;
case TERMINATED:
System.out.println("System is terminated, cannot perform any action");
break;
}
}
// 如果需要对状态进行转换,可以提供一个静态方法,确保其线程安全
public static synchronized State nextState(State currentState) {
switch (currentState) {
case ACTIVE:
return INACTIVE;
case INACTIVE:
return TERMINATED;
case TERMINATED:
throw new IllegalStateException("Cannot transition from terminated state");
}
}
}
在这个例子中,State
枚举表示系统可能的状态。performAction()
方法用于执行与当前状态相关的操作,而nextState()
方法用于安全地将状态从一种转换为另一种(确保了线程安全,因为它是静态同步的)。
2.3 如何在Java中创建线程安全的单例模式?(延伸)
双重检查锁定(Double-Check Locking, DCL)是一种创建线程安全单例的技巧,它在确保单例唯一性的同时,也尽可能地减少了同步带来的性能影响。以下是DCL单例的Java实现:
public class ThreadSafeSingleton {
private volatile static ThreadSafeSingleton instance;
private ThreadSafeSingleton() {}
public static ThreadSafeSingleton getInstance() {
if (instance == null) { // 第一次检查
synchronized (ThreadSafeSingleton.class) {
if (instance == null) { // 第二次检查
instance = new ThreadSafeSingleton();
}
}
}
return instance;
}
//
在这个例子中,我们首先检查instance
是否为null。如果instance
已经被初始化,则直接返回,避免不必要的同步。只有当第一次检查后instance
仍然为null,我们才进入同步块进行第二次检查。这是因为多个线程可能同时通过第一次检查,但只有一个线程能进入同步块。volatile
关键字确保了instance
变量被初始化后的可见性和有序性。
三、使用枚举实现单例模式相比传统的实现有什么优势?
使用枚举实现单例模式相比于传统实现(如饿汉式和懒汉式)有以下优势:
-
线程安全:Java枚举在加载时即被初始化,因此在多线程环境中无需额外的同步机制也能保证线程安全。
-
防止反序列化重新创建对象:传统的单例模式如果实现了Serializable接口,在进行序列化和反序列化时可能会创建新的对象,而枚举则不会出现这种情况。
-
简单易懂:枚举实现单例模式的代码简洁,结构清晰,易于理解和维护。
-
更符合语言规范:Java官方推荐使用枚举来实现单例模式,因为它能更好地防止对单例类的滥用和误用。
-
静态常量行为:枚举实例的行为类似于静态常量,它们都在类加载时初始化,且不可改变。
四、Java枚举有哪些特点?
Java中的枚举具有一些独特和强大的特点,包括:
-
类型安全:枚举是一种特殊的类,它只允许预定义的一组实例存在,这使得枚举在代码中使用时更加安全,避免了运行时错误。
-
自动序列化:Java枚举是自动可序列化的,无需额外实现Serializable接口。
-
枚举常量的集合:你可以通过
.values()
方法获取枚举的所有常量。 -
内置的方法:枚举自动提供了
name()
和ordinal()
方法,分别返回枚举常量的名字(即声明时的字符串)和其在枚举声明中的位置(从0开始)。 -
在枚举中定义方法:如上文所述,枚举可以直接定义方法,就像在任何类中一样。
-
枚举可以实现接口:虽然不能直接继承非抽象类,但枚举可以实现一个或多个接口,增加功能。
-
枚举构造器的访问限制:枚举的构造器默认是私有的,只能在枚举自己的定义内部被调用。
-
枚举开关语句:可以使用
switch
语句处理枚举值,方便进行多分支控制。
public class EnumDemo {
enum Color {
RED, GREEN, BLUE;
public void printColor() {
System.out.println(this.name());
}
}
public static void main(String[] args) {
for (Color color : Color.values()) {
color.printColor();
}
// Switch statement with enums
Color c = Color.RED;
switch(c) {
case RED:
System.out.println("Red color");
break;
case GREEN:
System.out.println("Green color");
break;
case BLUE:
System.out.println("Blue color");
break;
}
}
}
五、其他
枚举在实际开发中的应用场景有哪些?
枚举在实际开发中有多种应用,以下是一些常见的场景:
-
表示固定状态或选项:例如,一个任务的状态可能有"新建"、"进行中"、"已完成"等,这些可以用枚举来表示。
-
定义协议或标准:如HTTP的状态码,可以创建一个HTTPStatus枚举。
-
数据库列映射:如果数据库中有一个字段有固定的几个值,例如性别,可以创建Gender枚举进行映射。
-
表示角色或权限:在一个系统中,用户可能有不同的角色(如管理员、普通用户),或者不同的操作权限,可以使用枚举来定义。
-
编写游戏逻辑:游戏中常常会有一些预设的行为或动作,比如棋类游戏的棋子走法。
-
兼容API或框架:一些库或框架可能会要求使用特定的枚举类型作为参数。
-
算法中的标志位:在算法实现中,有时会用到一些标志位,这些也可以通过枚举来规范。
----------------------------------------------------------
在什么情况下,你不建议使用枚举?
不建议使用枚举的情况通常包括以下几点:
-
可扩展性需求:如果枚举的值预计在未来需要频繁增加或改变,这可能导致代码维护困难。因为枚举一旦定义,就不能动态添加新的成员。
-
需要动态实例化:枚举是编译时的常量集合,它们不能在运行时动态创建新的实例。
-
与数据库映射复杂:当数据库字段的值与枚举一一对应,但允许有额外未定义的值时,用枚举可能不合适,因为枚举无法表示未知或额外的数据。
-
性能考虑:虽然枚举在大部分情况下性能影响不大,但在极端情况下,大量使用枚举可能对内存占用和性能产生负面影响。
-
面向对象设计:若需要复杂的面向对象行为,枚举类不能直接继承其他类或实现接口(除非使用匿名内部类),这时可能需要更灵活的对象设计。
----------------------------------------------------------
你知道枚举的哪些特性使得它在某些情况下的可扩展性较差?
枚举在Java中的主要特点是:
-
安全性:枚举是类型安全的,意味着你只能使用定义在枚举中的特定值,无法赋值为其他无关类型的值。
-
确定性:枚举的实例数量是固定的,并且在编译时期就能确定,因此可以保证枚举的值不会在运行时意外改变。
-
自动序列化:枚举实现了Serializable接口,无需手动实现。
-
内建方法:枚举自动提供了
values()
方法来获取所有枚举实例的数组,以及valueOf()
方法将字符串转换为对应的枚举实例。 -
可以定义方法和字段:在枚举类型中可以定义方法和字段,提供额外的功能和属性。
-
不支持多继承:枚举不能直接继承其他类,但是可以通过实现接口来添加功能。
-
静态常量:每个枚举实例都是静态的,意味着你不需要通过new操作符来创建它们。
由于这些特性,枚举在一些情况下的可扩展性较差:
-
不可动态添加值:枚举在定义后不能添加新的实例,这意味着如果需要新增值,必须修改源代码并重新编译。
-
有限的继承能力:尽管可以实现接口,但不能直接继承其他类,这限制了其使用抽象类或具体类的能力,从而降低了灵活性。
-
封闭性:枚举的值在编译时就已经固定,无法在运行时根据需要调整。
-
对反射的支持有限:尽管枚举可以通过反射访问,但不能通过反射动态创建新的枚举实例。
基于上述特性,如果应用程序需要具有动态扩展性、继承或其他高级面向对象特性,枚举可能不是最佳选择。
----------------------------------------------------------
如何遍历所有的枚举常量?
在Java中,你可以通过以下方式遍历枚举的所有常量:
public enum Color {
RED, GREEN, BLUE;
}
public class Main {
public static void main(String[] args) {
// 遍历枚举常量
for (Color color : Color.values()) {
System.out.println(color);
}
}
}
这段代码会打印出RED
, GREEN
, 和 BLUE
。枚举类的values()
方法返回一个包含所有枚举常量的数组,这使得我们可以轻松地遍历它们。
----------------------------------------------------------
如何在枚举中添加自定义方法?
在Java中,您可以在枚举类型中定义自己的方法,就像在任何其他类中一样。以下是一个示例,展示了一个名为Color
的枚举,其中有一个自定义方法isDark()
:
public enum Color {
RED,
GREEN,
BLUE,
// 添加自定义方法
public boolean isDark() {
if (this == RED || this == BLACK) { // 假设还有BLACK枚举值
return true;
} else {
return false;
}
}
}
在这个例子中,我们定义了一个名为isDark()
的方法,用于检查颜色是否为深色。这个方法可以根据需要进行扩展,比如添加更多的枚举值或复杂的逻辑。
----------------------------------------------------------
Java中枚举有哪些内置的方法和特性?
在Java中,枚举具有一些内置的方法和特性,如下所述:
-
枚举常量:每个枚举都是由一组预定义的实例组成,这些实例是枚举类型的第一级成员,可以通过其名称直接访问。
-
values()
方法:返回枚举类型的全部实例作为数组。 -
valueOf(String name)
方法:根据给定的名称返回枚举常量。如果找不到匹配的常量,则会抛出IllegalArgumentException
。 -
ordinal()
方法:返回枚举常量的索引位置,通常从0开始,但这不是强制性的。 -
自动实现
Comparable
:枚举实例默认可以比较,它们按声明的顺序自然排序。 -
可以有构造函数:枚举类型可以有自己的构造函数,但是不能公开访问,只能私有或受保护。
-
可以定义方法和字段:枚举可以包含字段和方法,提供额外的功能。
-
可以实现接口:枚举类型可以实现一个或多个接口,添加自定义行为。
-
静态导入:枚举常量可以直接在代码中使用,无需每次都加上枚举名。
----------------------------------------------------------
枚举的私有构造方法使用场景是什么?举例说明
枚举的私有构造方法通常用于在枚举初始化时执行特定的操作,比如设置初始状态、计算某些值或者进行数据绑定等。这样的设计确保了枚举的实例只能通过其声明的方式被创建,并且在枚举的生命周期内,这些实例的状态不会改变。
例如,假设有一个表示星期的枚举,每个星期天都有一个对应的整数代表该周的天数:
public enum Weekday {
MONDAY(1),
TUESDAY(2),
WEDNESDAY(3),
THURSDAY(4),
FRIDAY(5),
SATURDAY(6),
SUNDAY(7);
private int dayOfWeek;
// 构造函数为私有以防止外部实例化
private Weekday(int dayOfWeek) {
this.dayOfWeek = dayOfWeek;
}
public int getDayOfWeek() {
return dayOfWeek;
}
}
在这个例子中,枚举的私有构造函数用于将给定的天数分配给相应的枚举实例。这样,当我们想要获取某个星期对应的一周中的天数时,就可以直接调用getDayOfWeek()
方法。
六、枚举应用实例
6.1 示例一
package com.ruoyi.common.enums;
/**
* 用户状态
*
* @author ruoyi
*/
public enum UserStatus {
OK("0", "正常"), DISABLE("1", "停用"), DELETED("2", "删除");
private final String code;
private final String info;
UserStatus(String code, String info) {
this.code = code;
this.info = info;
}
public String getCode() {
return code;
}
public String getInfo() {
return info;
}
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
SysUser user = userService.selectUserByUserName(username);
if (StringUtils.isNull(user)) {
log.info("登录用户:{} 不存在.", username);
throw new ServiceException(MessageUtils.message("user.not.exists"));
} else if (UserStatus.DELETED.getCode().equals(user.getDelFlag())) {
log.info("登录用户:{} 已被删除.", username);
throw new ServiceException(MessageUtils.message("user.password.delete"));
} else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
log.info("登录用户:{} 已被停用.", username);
throw new ServiceException(MessageUtils.message("user.blocked"));
}
passwordService.validate(user);
return createLoginUser(user);
}
6.2 示例二
略