一、背景

枚举经常被大家用来储存一组有限个数的候选常量。比如下面定义了一组常见数据库类型:

public enum DatabaseType {
MYSQL,
ORACLE,
SQLSERVER
}

当围绕这一组常量出现功能上的扩展点时,很多人的做法是为新的功能编写一个新类,新类中依赖该枚举类型。

比如要在界面上显示常见数据库类型的官方名称,可以用如下类实现这一功能:

public class DatabaseNameParser {
public String getDatabaseName(DatabaseType databaseType) {
if (DatabaseType.ORACLE.equals(databaseType)) {
return "Oracle数据库";
} else if (DatabaseType.MYSQL.equals(databaseType)) {
return "MySQL数据库";
} else if (DatabaseType.SQLSERVER.equals(databaseType)) {
return "SQL Server数据库";
} else {
throw new RuntimeException("不支持的数据库类型。");
}
} public static void main(String[] args) {
System.out.println(new DatabaseNameParser().getDatabaseName(DatabaseType.MYSQL));
}
}

大量的if - else语句以及对其他类的过渡依赖(几乎每两行代码就会引用一次DatabaseType对象),让上面这段代码散发出浓浓的坏味道,你可能会想如果这些逻辑可以整合在枚举类中实现就好了。答案是当然可以。

我们只需意识到枚举值不只是常量值,枚举值也是一种对象,他几乎拥有面向对象编程的绝大部分功能 -- 封装、多态、继承(不支持,但一定程度上可以模拟类似效果)。

二、用枚举进行面向对象编程

2.1 封装

上面的例子中,数据库类型的官方名称应该封装到每个数据库类型枚举对象中,作为一个属性字段,每个枚举值提供get方法即可直接通过枚举对象获取其对应的官方名称。如下:

public enum DatabaseType {
MYSQL("MySQL数据库"),
ORACLE("Oracle数据库"),
SQLSERVER("SQL Server数据库"); private final String name; DatabaseType(String name) {
this.name = name;
} public String getName() {
return name;
} public static void main(String[] args) {
System.out.println(MYSQL.getName());
}
}

2.2 多态

上面的例子中,比如新增一个需求,要在界面上提供数据库的连接检测功能。而不同数据库类型的连接检测逻辑是不同的。此时可以为枚举对象声明一个抽象方法,每个枚举值负责实现此方法。如下:

public enum DatabaseType {
MYSQL {
@Override
public boolean detect(String ip, int port) {
//为了简化举例环境,此处不真正实现功能
return false;
}
},
ORACLE {
@Override
public boolean detect(String ip, int port) {
return false;
}
},
SQLSERVER {
@Override
public boolean detect(String ip, int port) {
return false;
}
}; public abstract boolean detect(String ip, int port); public static void main(String[] args) {
System.out.println(MYSQL.detect("127.0.0.1", 3306)); }
}

这一特性也常被称之为:定义常量相关方法。

2.3 继承

枚举对象均是final对象,故不支持枚举对象的继承。但我们可以通过实现接口来模拟枚举类型的子类化,这也是子类化枚举类型的唯一方法。如下:

interface DatabaseType {
public boolean detect(String ip, int port); enum RDBMS implements DatabaseType {
MYSQL {
@Override
public boolean detect(String ip, int port) {
return false;
}
},
ORACLE {
@Override
public boolean detect(String ip, int port) {
return false;
}
},
SQLSERVER {
@Override
public boolean detect(String ip, int port) {
return false;
}
}
} enum NOSQL implements DatabaseType {
REDIS {
@Override
public boolean detect(String ip, int port) {
return false;
}
},
HBASE {
@Override
public boolean detect(String ip, int port) {
return false;
}
},
MONGODB {
@Override
public boolean detect(String ip, int port) {
return false;
}
}
}
}

这就模拟了DatabaseType派生出RDBMS和NOSQL两个抽象子类,每个子类下又派生出各自的具体实现类,这种继承结构。

三、用枚举更好地实现设计模式

当枚举值被赋予对象的强大能力后,再结合枚举类型本身的易用性,被聪明的Java开发者们发现了如下两类好用的模式:

3.1 使用枚举定义单例

—— 这是JDK5之后定义单例最好的方法,没有之一。

说起单例模式,常常让我们想起很多技巧或问题:

1、延迟初始化单例要Double Check,Double Check会失效需要使用volatile修饰符;

2、可以使用静态变量初始化单例;

3、单例对象可能可以被多次反序列化,就违背了单例模式的要求了;

... ...

现在不用纠结这些过时的套路了,就按如下方式定义单例,任何已知的问题都没有,一行代码搞定,而且引用起来更紧凑简洁。

public enum Singleton {
INSTANCE; public void anyFunction() {
} public static void main(String[] args) {
Singleton.INSTANCE.anyFunction();
}
}

3.2 使用枚举实现责任链或状态机

public enum StateMachine {
BEGIN {
@Override
public StateMachine handleAndReturnNextState() {
System.out.println("begin");
return PROCESS1;
}
},
PROCESS1 {
@Override
public StateMachine handleAndReturnNextState() {
System.out.println("process1");
if (true/*some condition*/) {
return PROCESS2;
}
return END;
}
},
PROCESS2 {
@Override
public StateMachine handleAndReturnNextState() {
System.out.println("process2");
return END;
}
},
END {
@Override
public StateMachine handleAndReturnNextState() {
System.out.println("end");
return null;
}
}; public abstract StateMachine handleAndReturnNextState(); public static void main(String[] args) {
StateMachine state = BEGIN;
while (state != null) {
state = state.handleAndReturnNextState();
}
}
}

四、总结

1、枚举不只是常量,也是对象,当与枚举相关的功能遇到扩展需求时,可以考虑在枚举对象上扩展功能点,以获得更加简洁紧凑的代码。

2、枚举是实现单例的最好方式。

3、枚举可以用来实现状态机。

Java进阶知识点1:白捡的扩展性 - 枚举值也是对象的更多相关文章

  1. Java进阶知识点: 枚举值

    Java进阶知识点1:白捡的扩展性 - 枚举值也是对象   一.背景 枚举经常被大家用来储存一组有限个数的候选常量.比如下面定义了一组常见数据库类型: public enum DatabaseType ...

  2. 5.7w字?GitHub标星120K的Java面试知识点总结,真就物超所值了

    如果你觉得在一些程序员平台获取到的资料太乱学习起来毫无头绪,但是单看<Java编程思想>相似的一类的Java圣经"枯燥无味",那我推荐你看一下这份GitHub获得过12 ...

  3. Java进阶知识点:协变与逆变

    一.背景 要搞懂Java中的协办与逆变,不得不从继承说起,如果没有继承,协变与逆变也天然不存在了. 我们知道,在Java的世界中,存在继承机制.比如MochaCoffee类是Coffee类的派生类,那 ...

  4. Java进阶知识点:更优雅地关闭资源 - try-with-resource

    一.背景 我们知道,在Java编程过程中,如果打开了外部资源(文件.数据库连接.网络连接等),我们必须在这些外部资源使用完毕后,手动关闭它们.因为外部资源不由JVM管理,无法享用JVM的垃圾回收机制, ...

  5. Java进阶知识点3:更优雅地关闭资源 - try-with-resource及其异常抑制

    一.背景 我们知道,在Java编程过程中,如果打开了外部资源(文件.数据库连接.网络连接等),我们必须在这些外部资源使用完毕后,手动关闭它们.因为外部资源不由JVM管理,无法享用JVM的垃圾回收机制, ...

  6. Java进阶知识点2:看不懂的代码 - 协变与逆变

    一.背景 要搞懂Java中的协办与逆变,不得不从继承说起,如果没有继承,协变与逆变也天然不存在了. 我们知道,在Java的世界中,存在继承机制.比如MochaCoffee类是Coffee类的派生类,那 ...

  7. Java进阶知识点:不要只会写synchronized - JDK十大并发编程组件总结

    一.背景 提到Java中的并发编程,首先想到的便是使用synchronized代码块,保证代码块在并发环境下有序执行,从而避免冲突.如果涉及多线程间通信,可以再在synchronized代码块中使用w ...

  8. Java进阶知识点:并发容器背后的设计理念

    一.背景 容器是Java编程中使用频率很高的组件,但Java默认提供的基本容器(ArrayList,HashMap等)均不是线程安全的.当容器和多线程并发编程相遇时,程序员又该何去何从呢? 通常有两种 ...

  9. Java进阶知识点:服务端高并发的基石 - NIO与Reactor AIO与Proactor

    一.背景 要提升服务器的并发处理能力,通常有两大方向的思路. 1.系统架构层面.比如负载均衡.多级缓存.单元化部署等等. 2.单节点优化层面.比如修复代码级别的性能Bug.JVM参数调优.IO优化等等 ...

随机推荐

  1. 【洛谷P2296】[NOIP2014]寻找道路

    寻找道路 题目链接 这道题非常的水,按照题意, 先反向建边,从终点搜索,标记出可以到达终点的点 然后枚举一遍,判断出符合条件1的点 再从起点搜索一遍就可以了 #include<iostream& ...

  2. 【luogu P3063 [USACO12DEC]牛奶的路由Milk Routing】 题解

    题目链接:https://www.luogu.org/problemnew/show/P3063#sub 我很好奇这道题为什么没被收入SPFA好题 #include <cstdio> #i ...

  3. 学大伟业 Day 6 培训总结

    今天接着昨天的继续讲数据结构 今天先是 分块 在统计问题中,尤其是序列问题,经常涉及到区间的操作,比如修改一段区间的元素,询问某个区间的元素的信息. 如果每次都对一整个区间的每一个元素进行操作的话,那 ...

  4. 【题解】洛谷P1463 [POI2002][HAOI2007] 反素数(约数个数公式+搜索)

    洛谷P1463:https://www.luogu.org/problemnew/show/P1463 思路 约数个数公式  ai为质因数分解的质数的指数 定理: 设m=2a1*3a2*...*pak ...

  5. ATK 设计框架 之 Atk.CustomExpression

    在ATK-DataPortal框架中的xxxHandel中常用到的一种类型,形如: 1.protected virtual D ItemHandle(D item, Func<E, E> ...

  6. 9.异常Exception

    9.1 异常概述 package exception; /* * 异常:程序运行的不正常情况 * * Throwable: 异常的超类 * |-Error * 严重问题,这种问题我们通过异常处理是不能 ...

  7. java中静态代理和动态代理

    一.概述 代理是一种模式,提供了对目标对象的间接访问方式,即通过代理访问目标对象.如此便于在目标实现的基础上增加额外的功能操作,前拦截,后拦截等,以满足自身的业务需求,同时代理模式便于扩展目标对象功能 ...

  8. multimap的使用

    multimap由于允许有重复的元素,所以元素插入.删除.查找都与map不同. 插入insert(pair<a,b>(value1,value2)) #include <iostre ...

  9. 高级同步器:交换器Exchanger

    引自:https://blog.csdn.net/Dason_yu/article/details/79764467 一.定义每个线程将条目上的某个方法呈现给 exchange 方法,与伙伴线程进行匹 ...

  10. 【PHP】统计问卷调查结果的选项票数和百分比

    遇到问题: 有以下数组,每一条记录是用户的每一条问卷题目的回答情况,q_id是问题id,o_id是选项id.需要统计每一个选项被选择的次数和每个选项占该问题的百分比.如问题1的选项有A和B,一个用户选 ...