Java之枚举

目录
枚举
引入
定义
代码示例
常用方法
代码示例
枚举的优缺点
枚举和反射
面试题
枚举
引入
枚举是在JDK1.5以后引入的。主要用途是:将一组常量组织起来,在这之前表示一组常量通常使用定义常量的方式:
publicstaticintfinalRED=1;
publicstaticintfinalGREEN=2;publicstaticintfinalBLACK=3;
但是常量举例有不好的地方,例如:可能碰巧有个数字1,但是他有可能误会为是RED,现在我们可以直接用枚举来进行组织,这样一来,就拥有了类型,枚举类型。而不是普通的整形1。
定义
在Java中,枚举类型是通过关键字enum来定义的。
枚举的定义类似于类的定义,但它使用enum关键字而不是class关键字。枚举可以包含字段、方法和构造函数,但构造函数默认是私有的,以防止外部代码创建枚举的实例。
本质:是 java.lang.Enum的子类,也就是说,自己写的枚举类,就算没有显示继承 Enum,但是它默认继承了这个类。
publicenumTestEnum{RED,BLACK,GREEN;
}
代码示例
public enum TestEnum {//枚举对象RED,WHITE,GREEN;public static void main(String[] args) {TestEnum testEnum = TestEnum.RED;switch (testEnum) {case RED:System.out.println("红色");break;case GREEN:System.out.println("绿色");break;case WHITE:System.out.println("白色");break;default:break;}}}
运行结果:

常用方法
| 方法名称 | 描述 |
| values() | 以数组形式返回枚举类型的所有成员 |
| ordinal() | 获取枚举成员的索引位置 |
| valueOf() | 将普通字符串转换为枚举类型 |
| compareTo() | 比较两个枚举类型成员在定义时的顺序 |
当枚举对象有参数后,需要提供相应的构造函数,枚举的构造函数默认是私有的。
代码示例
public enum TestEnum {
// 枚举对象RED(1,"RED"),WHITE(2,"WHITE"),GREEN(3,"GREEN");public String color;public int ordinal;private TestEnum(int ordinal,String color) {this.ordinal = ordinal;this.color = color;}public static void main(String[] args) {TestEnum[] testEnums = TestEnum.values();for (int i = 0; i < testEnums.length; i++) {System.out.println(testEnums[i]+" "+testEnums[i].ordinal());}System.out.println("=====");TestEnum testEnum = TestEnum.valueOf("RED");System.out.println(testEnum);System.out.println(RED.compareTo(WHITE));}
}
运行结果:

枚举的优缺点
优点
1.类型安全:枚举类型是编译时常量,它们比使用整数值或字符串常量更加安全。
2.自动封装:枚举类型提供了编译时的类型检查,确保了只有声明在枚举中的值才能被赋值给枚举类型的变量。
3.可以包含字段和方法:枚举类型可以拥有字段、方法和构造函数。
4.构造器限制:枚举的构造函数默认是私有的,防止外部代码实例化枚举。
5.实现接口:枚举类型可以实现一个或多个接口。
缺点:
1.不可变性限制:Java中的枚举实例默认是不可变的(即枚举值一旦创建,其状态就不能改变)。这种不可变性在大多数情况下是一个优点,因为它有助于保证线程安全和简化代码逻辑。然而,在某些情况下,你可能希望枚举值能够改变其状态,但这在Java枚举中是不被允许的。虽然你可以通过枚举中的方法改变枚举关联的其他对象的状态,但这通常不是最佳实践。
2.继承限制:Java中的枚举类型隐式地继承自java.lang.Enum类,并且Java不支持多重继承。因此,枚举类型不能继承自除java.lang.Enum之外的任何其他类。这限制了枚举的灵活性,并可能导致一些设计上的折衷。
枚举和反射
枚举是否能通过反射拿到实例对象呢?
代码示例
public enum TestEnum {
// 枚举对象RED(1,"RED"),WHITE(2,"WHITE"),GREEN(3,"GREEN");public String color;public int ordinal;private TestEnum(int ordinal,String color) {this.ordinal = ordinal;this.color = color;}public static void main(String[] args) throws ClassNotFoundException {Class<?> c = Class.forName("enumDemo.TestEnum");try {Constructor<?> constructor= c.getDeclaredConstructor(int.class,String.class);constructor.setAccessible(true);TestEnum testEnum= (TestEnum)constructor.newInstance(99,"hello");System.out.println(testEnum);} 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);}}
}
运行结果:

我们注意到,异常信息是:java.lang.NoSuchMethodException: enumDemo.TestEnum.<init>(int, java.lang.String)。
这是什么意思呢?就是没有对应的构造方法,但是我们明明已经提供了枚举的构造方法且两个参数分别是 int 和 String,那么问题出在哪里呢?
前面我们提到,所有的枚举类都默认继承于 java.lang.Enum,继承了父类除构造函数之外的所有内容,并且子类要帮助父类进行构造,而我们写的类并没有帮助父类构造,那是否意味着,我们要在实现的枚举类中提供 super 呢?
并不是,枚举类比较特殊,虽然我们实现的构造函数是两个参数,但是它默认还添加了两个参数,那添加的两个参数是什么呢?下面我们看一下Enum类的部分源码:

也就是说,我们自己的构造函数有两个参数一个是int一个是String,同时他默认在此前面还会给两个参数,一个是String一个是int。也就是说,这里我们正确给的是4个参数,修改后的代码:
public static void main(String[] args) throws ClassNotFoundException {Class<?> c = Class.forName("enumDemo.TestEnum");try {Constructor<?> constructor= c.getDeclaredConstructor(String.class,int.class,int.class,String.class);constructor.setAccessible(true);TestEnum testEnum= (TestEnum)constructor.newInstance("ceshi",999,99,"hello");System.out.println(testEnum);} 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);}
}
运行结果:

为什么此时在newInstance()方法会抛出异常: java.lang.IllegalArgumentException: Cannot reflectively create enum objects。
下面我们看看 newInstance() 方法的源码:

解释:
在这段代码中,if ((clazz.getModifiers() & Modifier.ENUM) != 0) 这一行是用来检查给定的类(clazz)是否是一个枚举(Enum)类型。
这里使用了位运算和位掩码的概念来检查类的修饰符中是否包含了枚举(Enum)的修饰符。在Java中,每个类都可以有一组修饰符,这些修饰符定义了类的性质,比如是否是公开的(public)、私有的(private)、受保护的(protected)、抽象的(abstract)、最终的(final)等,以及是否是枚举(enum)。这些修饰符在内部是通过整数(通常是int类型)的位模式来表示的,每个修饰符都对应一个特定的位位置。
Modifier.ENUM 是一个在 java.lang.reflect.Modifier 类中定义的常量,它代表了枚举类型的位掩码。这个常量是一个整数,其位模式中的某一位(或几位,但在这个上下文中通常只是一位)被设置为1,以表示枚举类型。
clazz.getModifiers() 方法返回了一个整数,这个整数包含了clazz类的所有修饰符的位模式。
& 是按位与(AND)运算符,它对两个整数进行操作,并返回一个新的整数,这个整数的每一位都是原来两个整数对应位进行AND操作的结果。如果两个整数在某一位上都是1,则结果在该位上也是1;否则,结果在该位上是0。
因此,clazz.getModifiers() & Modifier.ENUM 的结果是一个整数,它只在clazz的修饰符中包含枚举类型修饰符时才不为0。如果结果不为0,说明clazz是一个枚举类型;如果结果为0,说明clazz不是一个枚举类型。
所以,if ((clazz.getModifiers() & Modifier.ENUM) != 0) 这行代码的意思是:“如果clazz是一个枚举类型,则执行接下来的代码块(在这个例子中,是抛出一个IllegalArgumentException异常)”。这是因为在Java中,你不能通过反射来实例化枚举类型的对象,因为枚举的实例通常是通过枚举类型本身定义的常量来访问的,而不是通过构造函数创建的。
面试题
为什么枚举实现单例是线程安全的?
1.自动线程安全:Java的枚举类型是自动支持线程安全的。因为枚举类型本质上是通过类的静态字段来实现的,并且JVM保证了每个枚举实例在JVM中是唯一的。这意味着,在并发环境下,枚举实例的创建和访问都是线程安全的,无需额外的同步措施。
2.防止反射攻击:Java的枚举还有一个重要的特性,那就是它们默认是final的,并且枚举的构造函数默认也是私有的。这意味着枚举的实例不能被继承,也不能通过反射来调用枚举的构造函数来创建新的实例(尽管技术上可以通过反射调用私有构造函数,但Java平台禁止通过反射实例化枚举,如果尝试这样做,会抛出IllegalArgumentException或IllegalAccessException异常)。这种限制确保了枚举实例的唯一性和不可变性,从而增强了单例模式的安全性。
3.自动序列化机制:枚举类型还提供了自动的序列化机制。当枚举实例被序列化时,Java序列化机制仅仅是将枚举的name属性(即枚举常量的名称)输出到序列化流中,而不是像普通对象那样序列化其状态。反序列化时,Java通过枚举的name来查找枚举类型中对应的枚举常量,从而恢复枚举实例。这个机制确保了枚举实例在序列化和反序列化过程中仍然保持单例。
4.简单性和易用性:使用枚举实现单例模式非常简单,只需要定义一个枚举类型,并在其中定义一个枚举常量即可。这种实现方式既简洁又直观,易于理解和维护。
综上所述,枚举实现单例模式之所以被认为是安全的,主要是因为它提供了自动的线程安全、防止了反射攻击、支持自动序列化机制,并且实现简单、易用。这些特性使得枚举成为实现单例模式的理想选择之一。
相关文章:
Java之枚举
目录 枚举 引入 定义 代码示例 常用方法 代码示例 枚举的优缺点 枚举和反射 面试题 枚举 引入 枚举是在JDK1.5以后引入的。主要用途是:将一组常量组织起来,在这之前表示一组常量通常使用定义常量的方式: publicstaticintfinalRED1;…...
八、适配器模式
适配器模式(Adapter Pattern)是一种结构型设计模式,它允许不兼容的接口之间进行合作。适配器模式通过创建一个适配器类来转换一个接口的接口,使得原本由于接口不兼容无法一起工作的类可以一起工作。 主要组成部分: 目标…...
关于E-R图
一 什么是E-R图 E-R图(Entity-Relationship Diagram)是一种数据建模工具,用于描述数据库中实体之间的关系。它使用实体(Entity)、属性(Attribute)和关系(Relationship&#…...
DVWA通关教程
Brute Force Low 先进行一下代码审计 <?php // 检查是否通过GET请求传递了Login参数(注意:这里应该是username或类似的,但代码逻辑有误) if( isset( $_GET[ Login ] ) ) { // 从GET请求中获取用户名 $user $_GET[ us…...
网络学习-eNSP配置VRRP
虚拟路由冗余协议(Virtual Router Redundancy Protocol,简称VRRP) VRRP广泛应用在边缘网络中,是一种路由冗余协议,它的设计目标是支持特定情况下IP数据流量失败转移不会引起混乱,允许主机使用单路由器,以及即使在实际…...
Kafka【九】如何实现数据的幂等性操作
为了解决Kafka传输数据时,所产生的数据重复和乱序问题,Kafka引入了幂等性操作,所谓的幂等性,就是Producer同样的一条数据,无论向Kafka发送多少次,kafka都只会存储一条。注意,这里的同样的一条数…...
JavaScript知识点1
目录 1.JavaScript中常用的数组方法有哪些? 2.JavaScript的同源策略? 3.JavaScript中的 NaN 是什么? 4.JavaScript中的split、slice、splice函数区别? 1.JavaScript中常用的数组方法有哪些? 在 JavaScript 中&…...
51单片机个人学习笔记11(AT24C02-I2C总线)
前言 本篇文章属于STC89C52单片机(以下简称单片机)的学习笔记,来源于B站教学视频。下面是这位up主的视频链接。本文为个人学习笔记,只能做参考,细节方面建议观看视频,肯定受益匪浅。 [1-1] 课程简介_哔哩…...
创建Java项目,可实现main方法运行,实现对性能数据的处理
1、Android Studio无法执行Java类的main方法问题及解决方法 Android Studio无法执行Java类的main方法问题及解决方法_delegatedbuild-CSDN博客 D:\workspaces\performanceTools\.idea 文件夹下,gardle.xml ,添加依赖 <option name"delegatedBuild"…...
JavaWeb(后端)
MVC MVC 就是 Model View Controller 的缩写,属于一种软件架构设计模式一种思想,把我们的项目分为控制器(Controller)、模型(Model)、视图(view)三个部分,model就是处理…...
828华为云征文 | 华为云Flexusx实例,高效部署Servas书签管理工具的优选平台
前言 华为云Flexus X实例,Servas书签管理工具部署的优选平台!828节日特惠,让高效管理您的知识宝藏触手可及。Flexus X实例以其卓越的算力、灵活的资源配置和智能调优技术,为Servas提供了稳定、高效的运行环境。无论是快速访问、安…...
分治法和动态规划法
一、分治法(Divide and Conquer) 定义 分治法是一种将大问题分解成若干个小问题,递归地解决这些小问题,然后将这些小问题的解合并起来得到原问题的解的算法策略。(子问题之间相互独立) 基本步骤 1.分解…...
【FreeRL】我的深度学习库构建思想
文章目录 前言参考python环境效果已复现结果 综述DQN.py(主要)算法实现参数修改细节实现显示训练,保存训练 Buffer.pyevaluate.pylearning_curves 前言 代码实现在:https://github.com/wild-firefox/FreeRL 欢迎star 参考 动手学强化学习e…...
Docker部署nginx容器无法访问80端口
问题说明 在阿里云ECS服务器上部署一台CentOS服务器,然后在里面安装了docker服务。用docker部署了nginx,开启docker中的nginx服务,映射宿主机端口80 把阿里云服务器上面的安全组放开了80端口 但是还是无法访问nginx的80web界面 问题分析 查…...
Python语言开发学习之使用Python预测天气
什么是wttr? 使用Python预测天气的第一步,我们要了解wttr是什么。wttr.in是一个面向控制台的天气预报服务,它支持各种信息表示方法,如面向终端的ANSI序列(用于控制台HTTP客户端(curl、httpie或wget))、HTML(用于web浏览器)或PNG(…...
minio实现大文件断点续传
最近工作中遇到一个需求,用户需要上传大文件几百M,为了更好的用户体验,需要支持断点续传,秒传,上传进度条等功能。需求如下: 方案有两种: 第一种:前端直接将整个大文件丢到后端&…...
Qt绘制动态仪表(模仿汽车仪表指针、故障灯)
背景: 项目需要,可能需要做一些仪表显示。此篇除了介绍实现方法,还要说明心路历程。对我而言,重要的是心理,而不是技术。写下来也是自勉。 本人起初心里是比较抵触的,从业20多年了,深知所谓界…...
【视频教程】GEE遥感云大数据在林业中的应用与典型案例实践
近年来遥感技术得到了突飞猛进的发展,航天、航空、临近空间等多遥感平台不断增加,数据的空间、时间、光谱分辨率不断提高,数据量猛增,遥感数据已经越来越具有大数据特征。遥感大数据的出现为相关研究提供了前所未有的机遇…...
【时时三省】c语言例题----华为机试题<字符串排序>
山不在高,有仙则名。水不在深,有龙则灵。 ----CSDN 时时三省 1,题目 HJ14 字符串排序 描述 给定 n 个字符串,请对 n 个字符串按照字典序排列。 数据范围: 1≤n≤1000 1≤n≤1000 ,字符串长度满足 1≤l…...
基于vue框架的城市体育运动交流平台15s43(程序+源码+数据库+调试部署+开发环境)系统界面在最后面。
系统程序文件列表 项目功能:用户,赛事类型,近期赛事,比赛报名,器材类型,器材信息,自由约战,运动队伍 开题报告内容 基于Vue框架的城市体育运动交流平台开题报告 一、项目背景与意义 随着城市化进程的加速和居民健康意识的提升,城市体育运动已成为现代…...
深入剖析AI大模型:大模型时代的 Prompt 工程全解析
今天聊的内容,我认为是AI开发里面非常重要的内容。它在AI开发里无处不在,当你对 AI 助手说 "用李白的风格写一首关于人工智能的诗",或者让翻译模型 "将这段合同翻译成商务日语" 时,输入的这句话就是 Prompt。…...
【Java学习笔记】Arrays类
Arrays 类 1. 导入包:import java.util.Arrays 2. 常用方法一览表 方法描述Arrays.toString()返回数组的字符串形式Arrays.sort()排序(自然排序和定制排序)Arrays.binarySearch()通过二分搜索法进行查找(前提:数组是…...
Cesium1.95中高性能加载1500个点
一、基本方式: 图标使用.png比.svg性能要好 <template><div id"cesiumContainer"></div><div class"toolbar"><button id"resetButton">重新生成点</button><span id"countDisplay&qu…...
理解 MCP 工作流:使用 Ollama 和 LangChain 构建本地 MCP 客户端
🌟 什么是 MCP? 模型控制协议 (MCP) 是一种创新的协议,旨在无缝连接 AI 模型与应用程序。 MCP 是一个开源协议,它标准化了我们的 LLM 应用程序连接所需工具和数据源并与之协作的方式。 可以把它想象成你的 AI 模型 和想要使用它…...
spring:实例工厂方法获取bean
spring处理使用静态工厂方法获取bean实例,也可以通过实例工厂方法获取bean实例。 实例工厂方法步骤如下: 定义实例工厂类(Java代码),定义实例工厂(xml),定义调用实例工厂ÿ…...
【android bluetooth 框架分析 04】【bt-framework 层详解 1】【BluetoothProperties介绍】
1. BluetoothProperties介绍 libsysprop/srcs/android/sysprop/BluetoothProperties.sysprop BluetoothProperties.sysprop 是 Android AOSP 中的一种 系统属性定义文件(System Property Definition File),用于声明和管理 Bluetooth 模块相…...
聊一聊接口测试的意义有哪些?
目录 一、隔离性 & 早期测试 二、保障系统集成质量 三、验证业务逻辑的核心层 四、提升测试效率与覆盖度 五、系统稳定性的守护者 六、驱动团队协作与契约管理 七、性能与扩展性的前置评估 八、持续交付的核心支撑 接口测试的意义可以从四个维度展开,首…...
Selenium常用函数介绍
目录 一,元素定位 1.1 cssSeector 1.2 xpath 二,操作测试对象 三,窗口 3.1 案例 3.2 窗口切换 3.3 窗口大小 3.4 屏幕截图 3.5 关闭窗口 四,弹窗 五,等待 六,导航 七,文件上传 …...
mac 安装homebrew (nvm 及git)
mac 安装nvm 及git 万恶之源 mac 安装这些东西离不开Xcode。及homebrew 一、先说安装git步骤 通用: 方法一:使用 Homebrew 安装 Git(推荐) 步骤如下:打开终端(Terminal.app) 1.安装 Homebrew…...
scikit-learn机器学习
# 同时添加如下代码, 这样每次环境(kernel)启动的时候只要运行下方代码即可: # Also add the following code, # so that every time the environment (kernel) starts, # just run the following code: import sys sys.path.append(/home/aistudio/external-libraries)机…...
