在阅读这篇文章的内容之前我们可以思考一下,为什么在Java开发规范中有提到一点,尽量定义常量而不直接使用数值进行逻辑操作。其实避免"魔法值"主要是为了解决代码的可读性和可维护性这两个问题的。试想如果我们的项目当中到处散布着类似 if(type == 1){ // doSomething}else{ // doSomething}
这样的代码,那么日后接手我们工作的同事在看业务逻辑的时候会在心里如何问候你(PS:我艹,这写的什么玩意!)? 那么在我们工作当中具体是如何避免这种情况的发生呢?我想大家首先会想到的方式就如前面所提到的那样,定义常量然后再使用的时候直接调用,大概流程与下图类似:
public static final String PASSWORD_ERROR = "原密码错误";
public static final String USERCAPTCHA_ERROR = "验证码错误";
public static final String USER_PASSWORD_ERROR = "用户名或密码错误";
public static final String SYSTEM_ERROR = "系统异常";
if(password != user.getPassword()){
reutrn Constant.PASSWORD_ERROR ;
}
这样做有什么特点呢,首先我们定义了一些能够见名知义的常量,通过这些常量我们进行一些逻辑操作并不会破坏程序的可读性和可维护性(PS:最起码大家都能看得懂密码错误这个哈)。进一步,我们发现定义这些常量都是由static关键字所修饰的,这代表这些常量会在Constant类加载的时候被创建并初始化(这部分的数据事放在方法区当中的)。但是这样做也有一个不好的点,那就是不够灵活**。那就是常量在初始化之后便不可以被修改(PS:这里我们需要非常注意一点,那就是常量和常量引用的区别)。
我们可以通过如下的例子很直观的感受到:
而在实际的业务开发当中,我们有时可能会需要根据实际业务场景的需要,对数据进行修改。针对于这种情况,使用static final这种方式创建的常量并不能达到要求。因此我们可以考虑使用枚举类。
什么是枚举类?
先上一段百度百科对于枚举的介绍:
枚举类型在C#或C++,java,VB等一些计算机编程语言中是一种基本数据类型而不是构造数据类型,而在C语言等计算机编程语言中是一种构造数据类型 [1] 。它用于声明一组命名的常数,当一个变量有几种可能的取值时,可以将它定义为枚举类型。
枚举可以根据Integer、Long、Short或Byte中的任意一种数据类型来创建一种新型变量。这种变量能设置为已经定义的一组之中的一个,有效地防止用户提供无效值。该变量可使代码更加清晰,因为它可以描述特定的值。
那么我们再Java当中如何使用枚举类呢?一般都是这个样子的:
public enum DEFAULT_VALUE {
SPRING,SUMMER,AUTUMN,WINTER;
}
public static void main(String[] args) {
System.out.println(DEFAULT_VALUE.SPRING);
}
SPRING
Process finished with exit code 0
进一步的,我们可以更加深入的去了解枚举类到底是如何工作的。我们使用javac命令对该文件进行编译,然后使用javap命令看下反编译后生成的文件详情:
通过上述文件我们应该观察到几点:
- 该枚举类是由final关键字修饰的,这代表枚举类是无法被继承的。
- 该枚举类继承自一个叫java.lang.Enum<>的类。(而这里的泛型则是当前枚举类的类型)
- 我们定义的SPRING,SUMMER,AUTUMN,WINTER几个枚举变量,都是由static final关键字修饰的,并且该变量其实是我们当前定义的枚举类的一个实例(PS:这里我还是要给大家提醒,一定要搞清楚常量和常量引用的概念!!!)。
- 枚举类默认给我们提供了value()和valueOf()方法。
如果我们能够了解到这一点,那么我们就可以利用枚举类的这些特性去帮助我们实现一些业务场景,例如通过定义枚举类并声明枚举常量的方式在保证代码可读性的前提下支撑我们的业务逻辑判断;又或者利用枚举类帮助我们实现单例模式等。具体的代码我也放在下面的图上,希望大家在看的时候有自己的思考:
public enum DEFAULT_VALUE {
// 在写多个枚举类的时候,只有最后一个加分号
SYSTEM_ERROR("SYSTEM_ERROR","500"),
SUCCESS_ACCESS("SUCCESS","200") ;
private String key;
private String value;
DEFAULT_VALUE(String key,String value){
this.key = key;
this.value = value;
}
public void setKey(String key){
this.key = key;
}
public String getKey(){
return this.key;
}
public static void main(String[] args) {
System.out.println(DEFAULT_VALUE.SUCCESS_ACCESS.getKey());
DEFAULT_VALUE.SUCCESS_ACCESS.setKey("SUCCESSFULL");
System.out.println(DEFAULT_VALUE.SUCCESS_ACCESS.getKey());
}
}
SUCCESS
SUCCESSFULL
上面的这段代码描述了我们平时业务常用的场景,以这种方式来避免魔法值的产生,提高代码的可维护性。而接下来的这段代码,则是通过枚举类型来实现单例模式:
public enum Singleton{
INSTANCE;
private Object instance ;
Singleton(){
instance = new Object();
}
public Object getInstance(){
return instance;
}
}
public static void main(String[] args) {
Object instance1 = Singleton.INSTANCE.getInstance();
Object instance2 = Singleton.INSTANCE.getInstance();
System.out.println(instance1 == instance2);
}
true
Process finished with exit code 0
通过枚举类型实现单例模式,主要还是利用了上面提到的枚举类型的特点,首先INSTANCE常量其实是Singleton类型的一个实例,并且该实例是由static final修饰符修饰的,这代表着在Singleton类加载的时候,就已经初始化了该静态常量(PS:JVM可以保证静态类型初始化时的线程安全)。