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框架的城市体育运动交流平台开题报告 一、项目背景与意义 随着城市化进程的加速和居民健康意识的提升,城市体育运动已成为现代…...

2024年软件测试经典大厂面试题(全3套)【包含答案】
前言 金三银四即将过去,后面迎来的便是金九银十,一直想着说分享一些软件测试的面试题,这段时间做了一些收集和整理,下面共有三篇经典面试题,大家可以试着做一下,答案附在后面,希望能帮助到大家。…...

What is Node.JS and its Pros and Cons
What is Node.JS and its Pros and Cons JavaScript is a client-side development tool. Node.js is a server-side development tool. And it’s only a runtime environment based on Chrome V8 so we don’t write some code in Node.js. Pros: JavaScript on a server …...

TestCraft - GPT支持的测试想法生成器和自动化测试生成器
在当今快速变化的软件开发世界中,自动化测试已成为确保软件质量的关键环节。而随着AI技术的进步,越来越多的工具开始引入人工智能,来辅助生成测试用例和自动化测试脚本。其中,TestCraft,作为一款GPT支持的测试想法生成…...

FreeRTOS内部机制学习04(任务通知和软件定时器)
文章目录 何为任务通知?任务通知使用例子任务通知的优势以及劣势优势劣势 深入源码看看API函数内部干了什么函数的种类函数都做了啥? 软件定时器软件定时器的作用软件定时器内部到底做了什么实现了“闹钟”功能引入守护任务,守护任务做了啥&a…...

华为eNSP :WLAN的配置
一、WLAN的知识点: VLAN配置: VLAN:可以想象成一个大房子(网络)里划分的不同房间(VLAN)。每个房间可以有自己的功能,比如一个用于睡觉(管理),另一…...

中国大数据产业的融资热潮来袭,哪些领域最受资本青睐?
大数据产业是以数据及数据所蕴含的信息价值为核心生产要素,通过数据技术、数据产品、数据服务等形式,使数据与信息价值在各行业经济活动中得到充分释放的赋能型产业。 基于启信产业大脑的海量数据与专业研判模型,本文将从产业图谱、区域分析…...

Unity数据持久化 之 使用Excel.DLL读写Excel表格
本文仅作笔记学习和分享,不用做任何商业用途 本文包括但不限于unity官方手册,unity唐老狮等教程知识,如有不足还请斧正 终于找到一个比较方便容易读表的方式了,以前用json读写excel转的cvs格式文件我怎么使用怎么别扭…...

Linux系统:chown命令
1、命令详解: chown命令用于设置文件所有者和文件关联组的命令,全称为change directory。在Linux当中默认文件均有拥有者,可以利用 chown 将指定文件的拥有者改为指定的用户或组,输入参数时用户可以是用户名或者用户 ID࿰…...

Unity3D ARPG(动作角色扮演游戏)设计与实现详解
动作角色扮演游戏(Action Role-Playing Game, ARPG)结合了传统角色扮演游戏(RPG)的深度与动作游戏(Action Game)的即时反应和流畅战斗体验。Unity3D 作为一款强大的跨平台游戏开发引擎,为开发者…...

Qt实现登录界面
本文基于Qt实现一个简单的登录界面,主要使用到Widget、button、edit等控件,基于自定义的信号槽实现界面的跳转,使用绘图设备添加背景图等。 1. 创建主界面 设计主界面的样式,并添加相关的控件。如下显示: 代码如下&…...