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

微软PowerBI考试 PL300-选择 Power BI 模型框架【附练习数据】
微软PowerBI考试 PL300-选择 Power BI 模型框架 20 多年来,Microsoft 持续对企业商业智能 (BI) 进行大量投资。 Azure Analysis Services (AAS) 和 SQL Server Analysis Services (SSAS) 基于无数企业使用的成熟的 BI 数据建模技术。 同样的技术也是 Power BI 数据…...
模型参数、模型存储精度、参数与显存
模型参数量衡量单位 M:百万(Million) B:十亿(Billion) 1 B 1000 M 1B 1000M 1B1000M 参数存储精度 模型参数是固定的,但是一个参数所表示多少字节不一定,需要看这个参数以什么…...

遍历 Map 类型集合的方法汇总
1 方法一 先用方法 keySet() 获取集合中的所有键。再通过 gey(key) 方法用对应键获取值 import java.util.HashMap; import java.util.Set;public class Test {public static void main(String[] args) {HashMap hashMap new HashMap();hashMap.put("语文",99);has…...
深入浅出:JavaScript 中的 `window.crypto.getRandomValues()` 方法
深入浅出:JavaScript 中的 window.crypto.getRandomValues() 方法 在现代 Web 开发中,随机数的生成看似简单,却隐藏着许多玄机。无论是生成密码、加密密钥,还是创建安全令牌,随机数的质量直接关系到系统的安全性。Jav…...

(二)原型模式
原型的功能是将一个已经存在的对象作为源目标,其余对象都是通过这个源目标创建。发挥复制的作用就是原型模式的核心思想。 一、源型模式的定义 原型模式是指第二次创建对象可以通过复制已经存在的原型对象来实现,忽略对象创建过程中的其它细节。 📌 核心特点: 避免重复初…...

tree 树组件大数据卡顿问题优化
问题背景 项目中有用到树组件用来做文件目录,但是由于这个树组件的节点越来越多,导致页面在滚动这个树组件的时候浏览器就很容易卡死。这种问题基本上都是因为dom节点太多,导致的浏览器卡顿,这里很明显就需要用到虚拟列表的技术&…...
JAVA后端开发——多租户
数据隔离是多租户系统中的核心概念,确保一个租户(在这个系统中可能是一个公司或一个独立的客户)的数据对其他租户是不可见的。在 RuoYi 框架(您当前项目所使用的基础框架)中,这通常是通过在数据表中增加一个…...
【Go语言基础【13】】函数、闭包、方法
文章目录 零、概述一、函数基础1、函数基础概念2、参数传递机制3、返回值特性3.1. 多返回值3.2. 命名返回值3.3. 错误处理 二、函数类型与高阶函数1. 函数类型定义2. 高阶函数(函数作为参数、返回值) 三、匿名函数与闭包1. 匿名函数(Lambda函…...

R语言速释制剂QBD解决方案之三
本文是《Quality by Design for ANDAs: An Example for Immediate-Release Dosage Forms》第一个处方的R语言解决方案。 第一个处方研究评估原料药粒径分布、MCC/Lactose比例、崩解剂用量对制剂CQAs的影响。 第二处方研究用于理解颗粒外加硬脂酸镁和滑石粉对片剂质量和可生产…...
现有的 Redis 分布式锁库(如 Redisson)提供了哪些便利?
现有的 Redis 分布式锁库(如 Redisson)相比于开发者自己基于 Redis 命令(如 SETNX, EXPIRE, DEL)手动实现分布式锁,提供了巨大的便利性和健壮性。主要体现在以下几个方面: 原子性保证 (Atomicity)ÿ…...