Java进阶知识点1:白捡的扩展性 - 枚举值也是对象

 

一、背景

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

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. Java进阶知识点1:白捡的扩展性 - 枚举值也是对象

    一.背景 枚举经常被大家用来储存一组有限个数的候选常量.比如下面定义了一组常见数据库类型: public enum DatabaseType { MYSQL, ORACLE, SQLSERVER } ...

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

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

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

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

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

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

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

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

  6. Java进阶知识点:不可变对象与并发

    一.String的不可变特性 熟悉Java的朋友都知道,Java中的String有一个很特别的特性,就是你会发现无论你调用String的什么方法,均无法修改this对象的状态.当确实需要修改Strin ...

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

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

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

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

  9. Java进阶知识点6:并发容器背后的设计理念 - 锁分段、写时复制和弱一致性

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

随机推荐

  1. Gradle Goodness: Adding Tasks to a Predefined Group

    In Gradle we can group related tasks using the group property of a task. We provide the name of our ...

  2. 课时25.a标签基本使用(掌握)

    什么是a标签? 我们打开百度或者淘宝网页,观察任何一个超链接都会发现它有如下特质: 有下划线 移上去有小手指 可以点击 a标签的作用:就是用于控制页面与页面之间跳转的 a标签的格式:<a hre ...

  3. oracle 12如何解锁账户锁定状态及修改忘记的密码

    有两种方法,大同小异吧,感觉命令真是个好东西,哈哈哈哈,挽救了我安了4次才安好的oracle!!! 方法一: 1.如果忘记密码,找到忘记密码的是哪个用户身份,如果用户被锁定,可以使用下面说的方法解除锁 ...

  4. JavaScript变量声明及赋值

    1.变量声明 var a; //声明一个变量 a var b,c,d; //同时申明多个变量时,变量名之间用逗号隔开 console.log(a); //在控制台显示变量a的值,输出为undefine ...

  5. Linux字符设备学习,总结

    注册字符驱动的一种老方法: 注册一个字符设备的经典方法是使用:int register_chrdev(unsigned int major, const char *name, structfile_ ...

  6. VSCode 配置 C++

    每次换台电脑写c++,就要找配置,很是繁琐.这次自己写篇博客,记录下相关配置过程. 安装编译器 打开下面的网站 http://www.msys2.org ,下载 64 位的 MSYS2,按照主页上的步 ...

  7. 用Turtle库画一个爱心

    ---恢复内容开始--- 用Python中的turtle库画一个爱心 这个学期,我学了Python语言,并学到其中的一个库:turtle库.用turtle库可以画一些你想画的图片,所以我就想画一个爱心 ...

  8. html 的radio单选框如何实现互斥------radio只是input的type属性

    先看看没有互斥的情况: <html> <body> 男性:<input type="radio" id="male" /> ...

  9. python学习笔记:第7天 深浅拷贝

    目录 1. 基础数据类型补充 2. set集合 3. 深浅拷贝 1. 基础数据类型补充 (1)join方法 join方法是把一个列表中的数据进行拼接,拼接成字符串(与split方法相反,split方法 ...

  10. CDH集群搭建视频教程 百度云网盘下载

     CDH集群搭建视频教程 百度云网盘下载 链接: http://pan.baidu.com/s/1i5DVBlb   密码:2mny