枚举类型是Java 5中新增的特性,它是一种特殊的数据类型,之所以特殊是因为它既是一种类(class)类型却又比类类型多了些特殊的约束,但是这些约束的存在也造就了枚举类型的简洁性、安全性以及便捷性。当需要定义一组常量时,强烈建议使用枚举类。

使用枚举类的条件:类的对象是有限个,确定的。例如星期类,它的对象只有星期一…星期日七个,而且是确定的,此时就可以把星期类定义为一个枚举类;又例如性别类,它的对象只有男和女两个,而且是确定的,此时同样可以把性别类定义为一个枚举类;还有诸如季节等这种类的对象是有限个,确定的都可以定义为一个枚举类。

1、枚举类的实现

在JDK1.5之前,还没有枚举类型,如果想要使用枚举类需要我们去自定义。在自定义枚举类时需要注意以下几点:

(1)枚举类对象的属性不应允许被改动,所以应该使用 private final 进行修饰;

(2)枚举类使用 private final 修饰的属性应该在构造器中为其赋值;

(3)枚举类的构造器要私有化,保证不能在类的外部创建其对象,否则就不能确定对象的个数;

(4)在枚举类内部创建的枚举类的实例(枚举)对象,要声明为:public static final。

下面就拿季节举例,来自定义一个枚举类。

public class Season {//1.声明Season对象的属性,又因为枚举类对象的属性不应允许被改动, 所以应该使用 private final修饰//枚举类的使用 private final 修饰的属性应该在构造器中为其赋值private final String seasonName;private final String seasonDesc;//2.私有化构造器,保证不能在类的外部创建其对象,否则就不能确定对象的个数private Season(String seasonName,String seasonDesc){this.seasonName=seasonName;this.seasonDesc=seasonDesc;}//3.提供当前枚举类的多个枚举对象,又因为枚举类是不可变的常量类,所以需要声明为:public static finalpublic static final Season SPRING=new Season("春天","鸟语花香");public static final Season SUMMER=new Season("夏天","夏日炎炎");public static final Season AUTUMN=new Season("秋天","秋高气爽");public static final Season WINNER=new Season("冬天","寒风瑟瑟");//其他需求1:获取枚举类对象的属性//只需要提供属性的get方法即可,但是不能提供set方法,因为枚举类是不可变的常量类,不能被修改public String getSeasonName() {return seasonName;}public String getSeasonDesc() {return seasonDesc;}//其他需求2:打印对象,提供toString方法即可@Overridepublic String toString() {return "Season{" +"seasonName='" + seasonName + '\'' +", seasonDesc='" + seasonDesc + '\'' +'}';}
}
public class SeasonTest {public static void main(String[] args) {Season spring = Season.SPRING;System.out.println(spring); //Season{seasonName='春天', seasonDesc='鸟语花香'}}
}

在JDK 1.5 中新增了enum关键字用于定义枚举类,但是在使用时需要注意以下几点:

(1)使用 enum 定义的枚举类默认继承了 java.lang.Enum类,因此不能再继承其他类;

(2)使用 enum 定义的枚举类默认使用final进行修饰,不可以被继承;(也从侧面说明了它是一个常量类)

(3)枚举类的构造器只能使用 private 权限修饰符;

(4)枚举类的所有实例必须在枚举类中显式列出,多个对象之间使用",“隔开,末尾使用”;"结束。

列出的实例系统会自动添加 public static final 进行修饰;

(5)必须在枚举类的第一行声明枚举类对象;

(6)若枚举类只有一个枚举对象, 则可以作为一种单例模式的实现方式。

下面还是使用季节举例,来自定义一个枚举类。

//使用enum关键字定义枚举类
public enum  Season2 {//1.提供当前枚举类的对象,多个对象之间使用","隔开,末尾使用";"结束//系统默认使用public static final修饰SPRING("春天","鸟语花香"),SUMMER("夏天","夏日炎炎"),AUTUMN("秋天","秋高气爽"),WINNER("冬天","寒风瑟瑟");//2.声明Season对象的属性,又因为枚举类对象的属性不应允许被改动, 所以应该使用 private final修饰private final String seasonName;private final String seasonDesc;//3.枚举类的构造器只能使用 private 权限修饰符// 私有化构造器是为了保证不能在类的外部创建其对象,否则就不能确定对象的个数private Season2(String seasonName, String seasonDesc){this.seasonName=seasonName;this.seasonDesc=seasonDesc;}//其他需求:获取枚举类对象的属性//只需要提供属性的get方法即可,但是不能提供set方法,而且也不允许提供set方法,因为枚举类是不可变的常量类,不能被修改public String getSeasonName() {return seasonName;}public String getSeasonDesc() {return seasonDesc;}
}
public class SeasonTest {public static void main(String[] args) {Season2 spring = Season2.SPRING;System.out.println(spring);//SPRING}
}

2、Enum类中的常用方法

values()方法:返回枚举类型的对象数组,该方法可以很方便地遍历所有的枚举值;

//使用方法如下:

Season2[] seasons = Season2.values();
for (int i = 0; i < seasons.length; i++) {System.out.println(seasons[i]);
}
valueOf(String str):可以把一个字符串转为对应的枚举类对象。要求字符串必须是枚举类对象的“名字”。
如不是,会报运行时异常:IllegalArgumentException;
//使用方法如下:
Season2 spring = Season2.valueOf("SPRING");
System.out.println(spring);//SPRING
toString():返回当前枚举类对象的名称
//使用方法如下:
Season2 spring = Season2.SPRING;
System.out.println(spring.toString());//SPRING

3、使用enum关键字定义枚举类实现接口

枚举类和普通类一样,可以实现一个或多个接口。枚举类实现接口分为两种情况:

情况一:若枚举类的所有枚举对象在调用实现的接口方法时,呈现相同的行为方式,则只要统一实现该方法即可;此时与普通类实现接口一样,没有任何区别。

public interface Show {void show();
}
//使用enum关键字定义枚举类
public enum  Season2 implements Show{//1.提供当前枚举类的对象,多个对象之间使用","隔开,末尾使用";"结束//系统默认使用public static final修饰SPRING("春天","鸟语花香"),SUMMER("夏天","夏日炎炎"),AUTUMN("秋天","秋高气爽"),WINNER("冬天","寒风瑟瑟");//2.声明Season对象的属性,又因为枚举类对象的属性不应允许被改动, 所以应该使用 private final修饰private final String seasonName;private final String seasonDesc;//3.枚举类的构造器只能使用 private 权限修饰符// 私有化构造器是为了保证不能在类的外部创建其对象,否则就不能确定对象的个数private Season2(String seasonName, String seasonDesc){this.seasonName=seasonName;this.seasonDesc=seasonDesc;}//其他需求:获取枚举类对象的属性//只需要提供属性的get方法即可,但是不能提供set方法,而且也不允许提供set方法,因为枚举类是不可变的常量类,不能被修改public String getSeasonName() {return seasonName;}public String getSeasonDesc() {return seasonDesc;}//重写show()方法,与普通类实现接口一样,没有任何区别@Overridepublic void show() {System.out.println("一年四季:春夏秋冬");}
}
public class SeasonTest {public static void main(String[] args) {Season2 spring = Season2.SPRING;spring.show();Season2 summer = Season2.SUMMER;summer.show();Season2 autumn = Season2.AUTUMN;autumn.show();Season2 winner = Season2.WINNER;winner.show();}
}

运行结果:

情况二:若枚举类的每个枚举对象在调用实现的接口方法时,需要呈现出不同的行为方式,则可以让每个枚举对象分别来实现该方法

public interface Show {void show();
}
//使用enum关键字定义枚举类
public enum  Season2 implements Show{//1.提供当前枚举类的对象,多个对象之间使用","隔开,末尾使用";"结束//系统默认使用public static final修饰SPRING("春天","鸟语花香"){//每个枚举对象分别来实现该方法@Overridepublic void show() {System.out.println("春天是一个鸟语花香的季节!");}},SUMMER("夏天","夏日炎炎"){@Overridepublic void show() {System.out.println("夏天是一个夏日炎炎的季节!");}},AUTUMN("秋天","秋高气爽"){@Overridepublic void show() {System.out.println("秋天是一个秋高气爽的季节!");}},WINNER("冬天","寒风瑟瑟"){@Overridepublic void show() {System.out.println("冬天是一个寒风瑟瑟的季节!");}};//2.声明Season对象的属性,又因为枚举类对象的属性不应允许被改动, 所以应该使用 private final修饰private final String seasonName;private final String seasonDesc;//3.枚举类的构造器只能使用 private 权限修饰符// 私有化构造器是为了保证不能在类的外部创建其对象,否则就不能确定对象的个数private Season2(String seasonName, String seasonDesc){this.seasonName=seasonName;this.seasonDesc=seasonDesc;}//其他需求:获取枚举类对象的属性//只需要提供属性的get方法即可,但是不能提供set方法,而且也不允许提供set方法,因为枚举类是不可变的常量类,不能被修改public String getSeasonName() {return seasonName;}public String getSeasonDesc() {return seasonDesc;}
}
public class SeasonTest {public static void main(String[] args) {Season2 spring = Season2.SPRING;spring.show();Season2 summer = Season2.SUMMER;summer.show();Season2 autumn = Season2.AUTUMN;autumn.show();Season2 winner = Season2.WINNER;winner.show();}
}

运行结果:

4、枚举类对switch的语句的影响

Java1.5新增enum关键字的同时,也扩大了switch的语句使用范围。Java1.5之前,switch中的值只能是简单数据类型,比如int、byte、short、char, 有了枚举类型之后,就可以使用枚举类的对象了。同时在switch表达式中使用enum定义的枚举类的对象作为表达式时, case子句可以直接使用枚举对象的名字, 无需添加枚举类作为限定。这样一来,程序的控制选择就变得更加的方便,看下面的例子:

public enum  WeekDay {// 定义一周七天的枚举类型Monday,Tuesday, Wednesday ,Thursday,Friday,Saturday,Sunday;
}
class Test{public static void getDay(WeekDay weekDay){switch (weekDay){case Monday:System.out.println("Today is Monday");break;case Tuesday:System.out.println("Today is Tuesday");break;case Wednesday:System.out.println("Today is Wednesday");break;case Thursday:System.out.println("Today is Thursday");break;case Friday:System.out.println("Today is Friday");break;case Saturday:System.out.println("Today is Saturday");break;case Sunday:System.out.println("Today is Sunday");break;default:System.out.println("data error");}}public static void main(String[] args) {WeekDay sunday = WeekDay.Sunday;getDay(sunday);WeekDay friday = WeekDay.Friday;getDay(friday);}
}

运行结果:

对于这些枚举的日期,JVM都会在运行期构造成出一个简单的对象实例一一对应。这些对象都有唯一的identity,类似整型数值一样,switch语句就会根据此来identity进行执行跳转。

5、枚举类的线程安全问题

枚举类天生线程就是安全的,下面我们就来进行验证。

先写一个简单的枚举类,还是以季节类为例:

public enum Season {SPRING,SUMMER,AUTUMN,WINNER;
}

然后我们使用反编译,看看枚举类代码到底是怎么实现的,反编译后的代码内容如下:

public final class zzuli.edu.Season extends java.lang.Enum<zzuli.edu.Season> {public static final zzuli.edu.Season SPRING;public static final zzuli.edu.Season SUMMER;public static final zzuli.edu.Season AUTUMN;public static final zzuli.edu.Season WINNER;private static final zzuli.edu.Season[] $VALUES;public static zzuli.edu.Season[] values();public static zzuli.edu.Season valueOf(java.lang.String);private zzuli.edu.Season();static {};
}

由上述代码可知,每一个枚举类的枚举对象都是被public static final 进行修饰的,又因为被static修饰的属性在类加载的时候就会被加载,而且只会被加载一次,所以枚举类天生就是线程安全的。

6、枚举类实现单例模式

实现单例模式的方法有很多种,但是使用枚举类实现单例模式是最好、最安全的一种方式,这种方式也是Effective Java作者Josh Bloch 提倡的方式。因为它天生线程安全,不仅能避免多线程同步问题,而且还能防止使用反射重新创建新的对象。

使用枚举类实现单例模式非常简单,如下所示:

public enum  EnumSingle {INSTANCE;public EnumSingle getInstance(){return INSTANCE;}
}

下面使用代码进行测试,看创建的对象是否是单例:

public class Test {public static void main(String[] args) throws NoSuchMethodException {EnumSingle instance1 = EnumSingle.INSTANCE;EnumSingle instance2 = EnumSingle.INSTANCE;System.out.println(instance1==instance2);}
}

运行结果:

由运行结果可知,成功使用了单例模式。接下来测试使用反射能不能创建新的实例对象。

先来看一下使用反射创建实例对象newInstance方法的源码:

[图片上传失败…(image-8ba546-1633679059670)]

由newInstance方法的源码可知,反射在通过newInstance方法创建对象时,会先检查该类是否是枚举类,如果是,则会抛出IllegalArgumentException(“Cannot reflectively create enum objects”)异常,导致使用反射创建对象失败。下面我们就来测试一下:

先看一下枚举类的源码是有参构造函数还是无参构造函数,编译后的源码如下:

由源码可知,枚举类的构造函数为无参构造函数,下面就使用反射获取枚举类的无参构造函数,看使用反射是否能创建新的实例对象。

public class Test2 {public static void main(String[] args) throws Exception {EnumSingle instance1 = EnumSingle.INSTANCE;Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor();declaredConstructor.setAccessible(true);EnumSingle instance2 = declaredConstructor.newInstance();System.out.println(instance1);System.out.println(instance2);}
}

运行结果:

由运行抛出的异常可知,并不是我们预期的newInstance方法中的IllegalArgumentException(“Cannot reflectively create enum objects”)异常,而是NoSuchMethodException异常,说明枚举类中并没有无参构造函数,编译后的源码欺骗了我们。

接着我们通过javap反编译看一下枚举类的代码

[图片上传失败…(image-182813-1633679059670)]

public final class zzuli.edu.enumTest.EnumSingle extends java.lang.Enum<zzuli.edu.enumTest.EnumSingle> {public static final zzuli.edu.enumTest.EnumSingle INSTANCE;private static final zzuli.edu.enumTest.EnumSingle[] $VALUES;public static zzuli.edu.enumTest.EnumSingle[] values();public static zzuli.edu.enumTest.EnumSingle valueOf(java.lang.String);private zzuli.edu.enumTest.EnumSingle();public zzuli.edu.enumTest.EnumSingle getInstance();static {};
}

由上述反编译后的枚举类代码可知,反编译后的枚举类中存在的仍然是无参构造函数,说明反编译后的代码仍然骗了我们。下面我们就使用更专业的工具jad来进行反编译。

使用jad反编译后的枚举类源码如下所示:

public final class EnumSingle extends Enum
{public static EnumSingle[] values(){return (EnumSingle[])$VALUES.clone();}public static EnumSingle valueOf(String name){return (EnumSingle)Enum.valueOf(zzuli/edu/enumTest/EnumSingle, name);}private EnumSingle(String s, int i){super(s, i);}public EnumSingle getInstance(){return INSTANCE;}public static final EnumSingle INSTANCE;private static final EnumSingle $VALUES[];static {INSTANCE = new EnumSingle("INSTANCE", 0);$VALUES = (new EnumSingle[] {INSTANCE});}
}

由jad反编译后的源码可知,枚举类的构造函数为有参构造函数 EnumSingle(String s, int i),并且有两个参数。

下面我们就使用反射获取枚举类的有参构造函数,看使用反射是否能创建新的实例对象。

public class Test3 {public static void main(String[] args) throws Exception {EnumSingle instance1 = EnumSingle.INSTANCE;Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);declaredConstructor.setAccessible(true);EnumSingle instance2 = declaredConstructor.newInstance();System.out.println(instance1);System.out.println(instance2);}

运行结果:

由运行结果可知,与我们预期的异常一样,抛出了IllegalArgumentException(“Cannot reflectively create enum objects”)异常。
此时说明使用枚举类实现单例模式是十分安全的,使用反射进行暴力破解也不能创建新的对象。

你连简单的枚举类都不知道,还敢说自己会Java???滚出我的公司相关推荐

  1. java 包结构 枚举类_Java日期时间API系列6-----Jdk8中java.time包中的新的日期时间API类...

    因为Jdk7及以前的日期时间类的不方便使用问题和线程安全问题等问题,2005年,Stephen Colebourne创建了Joda-Time库,作为替代的日期和时间API.Stephen向JCP提交了 ...

  2. MySQL00-这都不知道还TM学啥MySQL

    目录 一.MySQL架构概述 1.1.客户端连接器 1.2.连接层 1.3.可插拔存储引擎 1.4.文件系统与文件 二.配置文件 三.数据文件 四.日志文件(以MySQL5.7.32为例) 4.1.错 ...

  3. C语言和我的世界指令哪个难,我的世界:这些指令你都不知道还敢说自己是老司机?...

    在我的世界中国版中有很多实用性的指令,这些指令可以让刚玩游戏的萌新得到一些帮助,也可以使开生存服的服主有效地控制和管理自己服务器的生态和秩序.那么今天坏坏为大家分享一些比较实用的指令,希望对萌新和还不 ...

  4. java 下拉列表 枚举_「Java三分钟」精准而优雅——枚举类详解

    关注我,每天三分钟,带你轻松掌握一个Java相关知识点. 1.为什么要用枚举 你在读一个老工程代码时,是否经常看见有几个类,里面放着成百上千的静态常量,场面相当恐怖,而且如果不加注释,很多你都不知道这 ...

  5. java枚举类是什么_Java学习--常用类(2)、Math类、枚举类

    1.日期时间类 小知识:在我们日常生活所使用的计算机端时间,实际上是对某一个特定时间的计数,即我们现在的时间离特定时间的间隔,这个间隔被称之为时间戳(timestamp),这个特定时间是:1970-0 ...

  6. 学妹问我Java枚举类与注解,我直接用这个搞定她!

    很多人问我学妹长什么样,不多说 上图吧! 学妹问我Java枚举类与注解,我直接一篇文章搞定! 一.枚举类 ① 自定义枚举类 ② enum关键字定义枚举类 ③ enum 枚举类的方法 ④ enum 枚举 ...

  7. enum java 比较_Kotlin与Java比较:枚举类

    前言 Kotlin作为JVM系的语言,起源于Java又不同于Java.通过在语言层面比较两者的区别,可以使得开发者能够快速学习,融会贯通. 枚举使用场景 使用枚举的场景非常明确,即只要一个类的对象是有 ...

  8. java枚举类型季节实例_Java之枚举类

    目录 一.为何引入枚举类型(为了替代魔法值) 什么是魔法值?魔法值有哪些隐患,见另一篇文章编码规约之使用Enum枚举类替代魔法值 那么为什么不用静态变量来替换魔法值呢? 有时候,变量的取值只在一个有限 ...

  9. java枚举类是怎么初始化的,为什么说枚举类是线程安全的

    今天写枚举类的时候发现了一个有趣的现象,在这里分享一下: 首先我们定义一个简单的枚举类: /*** @author jinghuaixin* @date 2020/04/30*/ public enu ...

最新文章

  1. LeetCode 200. Number of Islands--c++ dfs解法
  2. oracle rman imp exp,Oracle-client支持exp|imp|rman
  3. 做最轻量级的数据库中间层,赶紧学起来
  4. SpringBoot RabbitMQ 延迟队列代码实现
  5. Linux基础 —— Linux终端命令格式
  6. matlab编写数字基带信号程序,数字基带信号的系统仿真与设计matlab程序
  7. java数组有顺序吗_java – 使用特定顺序对(数组)列表进行排序
  8. Python 3 的新特性zz
  9. 企业级生产环境CICD入门
  10. 浙大学霸Facebook总部跳楼:永远不要把公司当成“家”
  11. opencv函数之saturate_cast(防止溢出)
  12. FX2LP与FPGA的简单批量回环
  13. centos7 里面dump_centos7使用lldb调试netcore应用转储dump文件
  14. 帮嫦娥五号登月的AI还能用来玩游戏,20行Python代码带你领略强化学习的风采
  15. 《赖氏经典英语语法》第六集
  16. 小米9开发版自带root吗_小米9root权限获取教程
  17. 初学Spring Cloud踩得坑之Caused by: org.springframework.context.ApplicationContextException
  18. 2022 最新 IntelliJ IDEA 2022 详细配置步骤演示(图文版)
  19. ubuntu pci wifi bcm4322 无法使用 解决方法
  20. 威斯康星麦迪逊计算机专业排名,威斯康星大学麦迪逊分校计算机专业详解

热门文章

  1. 盘点618 .NET 程序员必“败”书单
  2. .net core webapi 前后端开发分离后的配置和部署
  3. Rainbond 5.0正式发布, 支持对接管理已有Kubernetes集群
  4. 常见形式 Web API 的简单分类总结
  5. 使用C#实现适配器模式 (Adapter Pattern) 和外观模式 (Facade Pattern)
  6. 弹性和瞬态故障处理库Polly介绍
  7. ASP.NET Core 网站在Docker中运行
  8. .NET应用迁移到.NET Core(一)
  9. 【ArcGIS风暴】ArcGIS10.6创建LAS数据集的两种方法并加载点云数据
  10. 学习SQL数据查询,这一篇就够了!