Java枚举解读
Java枚举
枚举类概念的理解与定义
- 一个类的对象是有限个,确定的,我们称此为枚举类。
- 当需要定义和维护一组常量时,强烈建议使用枚举类。
- 如果一个枚举类中只有一个对象,则可以作为单例模式的实现方式。
通俗的说:一个类被设计为包含固定实例数量的特殊类,我们给他的定义是枚举类。
注意:
1.枚举类不能被 new 出来,枚举类因为默认的类修饰符为 final 所以也不能被派生(继承),同理枚举类也不能为当作实现。
2.枚举类自身可以实现接口,既可以进行统一实现重写接口抽象方法,也可以按照枚举类型单个实现重写。
枚举类的定义
关于枚举类的定义,这块主要想和大家分享两种方式
- jdk 5.0之前,自定义枚举类方式
- jdk 5.0之后,Enum关键字方式定义
实践
一、准备工作
我们新建一个 Java Project ,并创建一个包,以及一个测试类
二、自定义枚举的三种方式(jdk 5.0 之前)
1. 定义一个抽象类,在抽象类中定义常量进行维护,我们接下来以 Java 类库中的 Calendar 类示例来进行说明
新建一个类 EnumDemo01.java 代码如下:
package org.taoguoguo;
import java.util.Calendar;
/**
* @author taoGG
* @description jdk 5.0 之前 抽象类枚举方案Demo
* @create 2020-09-13 14:20
*/
public class EnumDemo01 {
public static void main(String[] args) {
Calendar calendar = Calendar.getInstance();
System.out.println(calendar.get(1));
}
}
Console 结果输出:
2020
Process finished with exit code 0
如果熟悉 Calendar API 的小伙伴 应该马上能反应过来,这个是获取当前的年份,类似的值还有
3 - 一年中的第几个星期
4 - 一年中的第几个月
5 - 当前的日期
......
但是这么多值,我们怎么能记得住呢?万一我输入错误,随便取了一个范围怎么办?
没错,这是 jdk 5.0之前的痛点,为了解决实例数量固定,便于维护这些问题,在jdk 5.0之后更新Enum枚举类解决了这个问题。那在jdk 5.0之前官方是怎么做的呢?难道需要我们一个个去记住 Calendar 的数字?
实际上官方本身,采用的就是我们现在说的第一种方式,在抽象类中定义常量进行维护
现在我们将代码做些修改:
package org.taoguoguo;
import java.util.Calendar;
/**
* @author taoGG
* @description jdk 5.0 之前 抽象类枚举方案Demo
* @create 2020-09-13 14:20
*/
public class EnumDemo01 {
public static void main(String[] args) {
Calendar calendar = Calendar.getInstance();
System.out.println(calendar.get(Calendar.YEAR));
}
}
我们运行进行输出:
2020
Process finished with exit code 0
结果与之前一致,这时我们就清楚,在开发过程中作为开发者我们肯定愿意使用 Calendar.YEAR 这种写法,一来方便记忆,二来可读性高。那么官方的做法时怎样的呢?我们点进去源码看一下
-
首先 Calendar 本身是一个抽象类,实现了序列化、克隆、以及比较排序接口,这边和我们枚举没有太大关系,我们继续往下看
-
在抽象类中,定义了很多个静态常量进行维护,而当我们需要使用时,直接调用,这样就比我们写一个个的具体值要方便和易用了。
2. 定义一个接口,在接口中定义常量维护枚举值
我们新建一个interface CustomerInf.java
package org.taoguoguo;
/**
* @author taoGG
* @description 接口常量维护枚举值
* @create 2020-09-13 15:47
*/
public interface CustomerInf {
int RED = 1;
int GREEN = 2;
int BLUE = 3;
}
在 EnumTest 进行测试
package org.taoguoguo;
/**
* @author taoGG
* @description Java枚举测试类
* @create 2020-09-13 14:54
*
*/
public class EnumTest {
public static void main(String[] args) {
System.out.println(CustomerInf.RED);
}
}
测试结果:
1
Process finished with exit code 0
这种做法我们达到了和在抽象类中维护常量相同的目的。上面这两种做法都非常的简单易用,但也有弊端。比如我们只知道一个状态值,当我们要获取状态的属性或者相关的内容时,我们该怎么做呢?
下面我们使用第三种方式,自定义枚举类,这种基本上达到和 Enum 关键字相同的作用,但有一点不足就是会较为复杂
3.自定义枚举类,通过为类私有化构造器和固定实例对象进行枚举维护
新建一个class SeasonEnum.java,代码如下:
package org.taoguoguo;
/**
* @author taoGG
* @description
* @create 2020-09-13 15:58
*/
public class SeasonEnum {
//1.声明枚举对象的属性
private final String seasonName;
private final int code;
//2.私有化类的构造器
private SeasonEnum(String seasonName,int code){
this.seasonName = seasonName;
this.code = code;
}
//3.提供当前枚举类的多个对象 public static final
public static final SeasonEnum SPRING = new SeasonEnum("春天",100);
public static final SeasonEnum SUMMER = new SeasonEnum("夏天",200);
public static final SeasonEnum AUTUMN = new SeasonEnum("秋天",300);
public static final SeasonEnum WINTER = new SeasonEnum("冬天",400);
//4.为类提供获取属性的方法
public String getSeasonName() {
return seasonName;
}
public int getCode() {
return code;
}
//5.重写toString方法
@Override
public String toString() {
return "SeasonEnum{" +
"seasonName='" + seasonName + '\'' +
", code=" + code +
'}';
}
}
新建一个class SeasonEnumTest 进行测试,当我们通过自定义枚举类引用实例对象时,如下图可以看到,我们已经可以获取到我们的枚举对象了。
获取到枚举对象,我们当然也可以获取到对应的属性及方法,这种可用性就提高了很多,我们在开发程序进行判断,可以根据各种枚举值的指定属性来进行,提高了代码的可维护性。
SeasonEnumTest 测试代码
package org.taoguoguo;
/**
* @author taoGG
* @description
* @create 2020-09-13 16:04
*/
public class SeasonEnumTest {
public static void main(String[] args) {
SeasonEnum spring = SeasonEnum.SPRING;
System.out.println("自定义枚举类对象:" + spring);
System.out.println("自定义枚举类属性:" + spring.getSeasonName());
System.out.println("自定义枚举类属性:" + spring.getCode());
}
}
根据我们上面的自定义枚举类方式,我们基本已经实现了枚举的功能了,但是就像上面说到的,如果开发中枚举类型较多,开发多个这样的自定义枚举类会非常的耗时,所以 jdk 5.0 之后,推出了 Enum 关键字定义枚举类
三、Enum 关键字定义枚举类(jdk 5.0之后)
enum
全称为 enumeration,是jdk 5.0 中引入的新特性,在Java 中被 enum
关键字修饰的类型就是枚举类型
我们通过代码来示例来讲解和理解 enum
的用法,还是用我们刚刚自定以枚举类的例子,看看使用enum
如何来写
新建一个Java class ,Kind 类型选择 enum
如图:
枚举类创建注意:
- 枚举实例必须在
enum
关键字声明的类中显式的指定(首行开始的以第一个分号结束) - 枚举不允许使用new,clone,反射,序列化手动创建枚举实例
package org.taoguoguo;
/**
* @author taoGG
* @description
* @create 2020-09-13 16:23
*/
public enum Season {
SPRING("春天",100),
SUMMER("夏天",200),
AUTUMN("秋天",300),
WINTER("冬天",400);
private final String seasonName;
private final int code;
Season(String seasonName, int code){
this.seasonName = seasonName;
this.code = code;
}
public String getSeasonName() {
return seasonName;
}
public int getCode() {
return code;
}
}
使用 SeasonTest 测试类进行测试:
package org.taoguoguo;
/**
* @author taoGG
* @description
* @create 2020-09-13 16:27
*/
public class SeasonTest {
public static void main(String[] args) {
Season spring = Season.SPRING;
System.out.println(spring);
}
}
输出结果:
SPRING
Process finished with exit code 0
注意,在enmu 枚举类中如果没有重写 toString方法,会默认使用Enum类本身提供的 toString 方法,返回枚举类名称,因为定义的枚举类默认隐式继承于java.lang.Enum
1.枚举类主要方法介绍
values()
:该方法可以返回当前枚举类型的对象数组,可以很方便的遍历所有枚举值。一般我们可以根据枚举类的相关属性通过此方法遍历获取对应的枚举对象及枚举值valueOf(String str)
: 根据枚举类名称获取枚举类对象toString()
: 默认使用 java.lang.Enum的 toString方法,返回当前对象常量的名称,枚举类推荐重写返回自定义友好描述name()
: 返回当前枚举对象名称,和toString作用上类似,当时toString支持重写,name方法是不能重写的,在本质上 toString 也是调用的 name方法,枚举定义 name 方法就是为了返回枚举对象名称,而 toString 应该根据需要进行重写ordinal()
: 返回当前枚举对象的序号, 实现了 Comparable 接口,表明它是支持排序的 可以通过Collections.sort
进行自动排序比较此枚举与指定对象的顺序compareTo()
: 基于ordinal
进行序号大小比较
方式演示代码,小伙伴们可以自行运行输出一下,看看各个方法的作用,熟悉一下相关的方法api
package org.taoguoguo;
/**
* @author taoGG
* @description
* @create 2020-09-13 16:27
*/
public class SeasonTest {
public static void main(String[] args) {
System.out.println("========values()方法=======");
for (Season season : Season.values()) {
System.out.println(season);
}
System.out.println("===========================");
System.out.println("========valueOf方法========");
Season spring = Season.valueOf("SPRING");
System.out.println(spring);
System.out.println("===========================");
System.out.println("========toString方法========");
System.out.println(spring.toString());
System.out.println("===========================");
System.out.println("========name方法========");
System.out.println(spring.name());
System.out.println("===========================");
System.out.println("========ordinal方法========");
System.out.println(spring.ordinal());
System.out.println("===========================");
System.out.println("========compareTo方法========");
System.out.println(spring.compareTo(Season.WINTER));
System.out.println("===========================");
}
}
2.枚举类对接口的实现方式
准备工作
新建一个EnumInf
接口,定义一个抽象方法
package org.taoguoguo;
/**
* @author taoGG
* @description
* @create 2020-09-13 17:25
*/
public interface EnumInf {
void show();
}
1.实现接口,在enum中统一实现抽象方法
新建一个EnumInf
接口,定义抽象方法 show()
package org.taoguoguo;
/**
* @author taoGG
* @description
* @create 2020-09-13 17:25
*/
public interface EnumInf {
void show();
}
新建一个OrderStatus
枚举类 实现 EnumInf
接口
package org.taoguoguo;
/**
* @author taoGG
* @description
* @create 2020-09-13 17:27
*/
public enum OrderStatus implements EnumInf{
SUCCESS(200,"交易成功"),
Fail(500,"交易失败");
private final int code;
private final String desc;
OrderStatus(int code, String desc){
this.code = code;
this.desc = desc;
}
public int getCode() {
return code;
}
public String getDesc() {
return desc;
}
/**
* 第一种方式,枚举统一重写接口抽象方法
*/
@Override
public void show() {
System.out.println("订单枚举对象");
}
}
进行测试
package org.taoguoguo;
/**
* @author taoGG
* @description
* @create 2020-09-13 17:32
*/
public class OrderStatusTest {
public static void main(String[] args) {
OrderStatus success = OrderStatus.SUCCESS;
success.show();
}
}
输出结果
订单枚举对象
Process finished with exit code 0
跟我们常用类实现没有什么区别,枚举也是可以统一实现的,那如果想针对不同的枚举对象进行不同状态的实现怎么办呢?比如我们的OA系统、或者电商系统中,根据不同状态 我们需要回写对应的数据,下面我们就来看看如何实现。
2.枚举对象分别实现接口中的抽象方法
案例跟接口统一实现一致,我们这边修改一下OrderStatus
枚举类,代码如下
package org.taoguoguo;
/**
* @author taoGG
* @description
* @create 2020-09-13 17:27
*/
public enum OrderStatus implements EnumInf{
SUCCESS(200,"交易成功") {
@Override
public void show() {
System.out.println("回写交易成功状态");
}
},
Fail(500,"交易失败") {
@Override
public void show() {
System.out.println("回写交易失败状态");
}
};
private final int code;
private final String desc;
OrderStatus(int code, String desc){
this.code = code;
this.desc = desc;
}
public int getCode() {
return code;
}
public String getDesc() {
return desc;
}
}
我们再修改下测试类代码:
package org.taoguoguo;
/**
* @author taoGG
* @description
* @create 2020-09-13 17:32
*/
public class OrderStatusTest {
public static void main(String[] args) {
OrderStatus success = OrderStatus.SUCCESS;
success.show();
OrderStatus fail = OrderStatus.Fail;
fail.show();
}
}
输出结果
回写交易成功状态
回写交易失败状态
Process finished with exit code 0
通过这种方式就可以轻而易举地定义每个枚举实例不同的行为方式,也达到了我们预期的效果,其实在开发过程中根据枚举的设计和设计模式的铺垫可以极大的简化我们的业务代码。
3.Enum枚举类的工具类及应用场景
1.EnumSet 和 EnumMap
Java 中提供了两个方便操作enum的工具类——EnumSet 和 EnumMap。
EnumSet
是枚举类型的高性能 Set
实现。它要求放入它的枚举常量必须属于同一枚举类型。
// EnumSet的使用
System.out.println("EnumSet展示");
EnumSet<OrderStatus> errSet = EnumSet.allOf(OrderStatus.class);
for (OrderStatus e : errSet) {
System.out.println(e.name() + " : " + e.ordinal());
}
EnumMap
是专门为枚举类型量身定做的 Map
实现。虽然使用其它的 Map 实现(如HashMap)也能完成枚举类型实例到值得映射,但是使用 EnumMap 会更加高效:它只能接收同一枚举类型的实例作为键值,并且由于枚举类型实例的数量相对固定并且有限,所以 EnumMap 使用数组来存放与枚举类型对应的值。(计算机处理连续的资源使用局部内存效率更高)这使得 EnumMap 的效率非常高。
// EnumMap的使用
System.out.println("EnumMap展示");
EnumMap<StateMachine.Signal, String> errMap = new EnumMap(StateMachine.Signal.class);
errMap.put(StateMachine.Signal.RED, "红灯");
errMap.put(StateMachine.Signal.YELLOW, "黄灯");
errMap.put(StateMachine.Signal.GREEN, "绿灯");
for (Iterator<Map.Entry<StateMachine.Signal, String>> iter =errMap.entrySet().iterator(); iter.hasNext();) {
Map.Entry<StateMachine.Signal, String> entry = iter.next();
System.out.println(entry.getKey().name() + " : " + entry.getValue());
}
2.枚举类与 Switch 的配合使用
关于枚举与switch是个比较简单的话题,使用switch进行条件判断时,条件参数一般只能是整型,字符型。而枚举型确实也被switch所支持,在java 1.7后switch也对字符串进行了支持。
实践
新建一个 BizEnum
的java class,代码如下
package org.taoguoguo;
/**
* @author taoGG
* @description 企业类型枚举
* @create 2020-09-13 21:24
*/
public enum BizEnum {
COUNTRIES(101,"国有企业"),
PRIVETE(102,"私营企业"),
SOHO(103,"个体单位");
private final int code;
private final String desc;
BizEnum(int code, String desc){
this.code = code;
this.desc = desc;
}
public int getCode() {
return code;
}
public String getDesc() {
return desc;
}
//根据编码获取当前枚举对象的方法
public static BizEnum getBizTypeByCode(int code){
for (BizEnum bizEnum : BizEnum.values()) {
if(code == bizEnum.getCode()){
return bizEnum;
}
}
return null;
}
}
结合Switch
进行测试
package org.taoguoguo;
/**
* @author taoGG
* @description
* @create 2020-09-13 21:31
*/
public class BizTest {
public static void main(String[] args) {
BizEnum bizType = BizEnum.getBizTypeByCode(101);
switch (bizType){
case COUNTRIES:
System.out.println("国有企业");
break;
case PRIVETE:
System.out.println("私营企业");
break;
case SOHO:
System.out.println("个体单位");
break;
default:
System.out.println("创业中");
}
}
}
输出结果:
国有企业
Process finished with exit code 0
总结
- jdk 5.0之前我们可以自定义枚举类,jdk 5.0之后使用
enum
关键字定义枚举类,枚举类默认继承自java.lang.Enum
,使用枚举类将常量组织起来,便于统一管理。例如错误码
、状态机
等场景中,较为合适使用枚举类。 - 枚举类常用方法介绍及枚举类实现抽象类、接口等抽象方法的两种方式。
- 枚举常用的工具类及与
switch
使用的场景。