一. 什么是魔法值
魔法值通常是指在编写代码时凭空出现的数字或字符串,如果没有注释,无法直接判断其代表的含义,必须通过分析代码上下文才能明白。
魔法值会严重降低代码可读性与可维护性。在一个周期相对较长的项目中,很可能后续在回顾之前编写的代码时忘记这类魔法值代表的含义,如果没有注释或批注,将严重影响开发进度。个人项目亦是如此,更何况别人的项目代码。
例如:
gender.setGender(0); // 设置性别为女
gender.setGender(1); // 设置性别为男statu.setStatus(0); //表示状态正常
statu.setStatus(1); //表示状态异常……
阿里巴巴 Java 开发规范手册中明确指出:不允许任何魔法值(即未经定义的常量)直接出现在代码中。
二. 魔法值替换
1. 常量
使用 public static final 修饰的成员变量,必须有初始化值,且一旦被初始化,执行过程中值将不可变。通常使用英文单词全部大写,多个单词之间使用下划线连接。
// 标识用户性别
public class UserGender {// 0 代表女性public static final Integer USER_GENDER_FEMALE = 0;// 1 代表男性public static final Integer USER_GENDER_MALE = 1;}// 标识用户状态常量
public class UserStatus {// 0 代表正常 public static final Integer USER_NORMAL = 0;// 1 代表异常public static final Integer USER_ABNORMAL = 1;// 2 代表禁止public static final Integer USER_PROHIBIT = 2;
}
在使用常量时,Java 会在编译阶段自动进行替换,将常量替换成真实的字面量。
// 使用常量 UserGender.USER_GENDER_MALE
System.out.println(UserGender.USER_GENDER_MALE);
// java会在编译阶段替换为字面量 1
System.out.println(1);
在业务中具体使用时可以显著提高代码可读性,如果想要修改值,在 UserGender 类中修改即可,所有引用处都会生效。
// 使用魔法值
user.setGender(0);// 使用常量
user.setGender(UserGender.USER_GENDER_FEMALE);
2. 枚举类
通过枚举类,开发者可以固定要接收的常量数值,适合需要约束接收值的场景。
对于相对简单的场景,直接使用枚举示例:
// 协议状态枚举
public enum ProtocolMessageStatusEnum {OK, // 请求成功BAD_REQUEST, // 请求失败BAD_RESPONSE; // 响应失败}
使用时直接调用,但只能获取事先罗列的枚举类实例:
public class Test {public static void main(String[] args) {System.out.println(ProtocolMessageStatusEnum.OK);System.out.println(ProtocolMessageStatusEnum.BAD_REQUEST);System.out.println(ProtocolMessageStatusEnum.BAD_RESPONSE);}
}
在相对复杂的场景中,可以给每个实例增加一个或多个值。
例如在 RPC 项目实现自定义协议部分,曾创建响应状态的枚举类:
import lombok.Getter;/*** 协议消息的状态枚举*/
@Getter
public enum ProtocolMessageStatusEnum {OK("ok", 20),BAD_REQUEST("badRequest", 40),BAD_RESPONSE("badResponse", 50);// 可以有多个字段private final String text;private final int value;// 必须提供对应参数构造器ProtocolMessageStatusEnum(String text, int value){this.text = text;this.value = value;}/*** 根据 value 获取枚举** @param value* @return*/// 开放获取实例值权限public static ProtocolMessageStatusEnum getEnumByValue(int value){for (ProtocolMessageStatusEnum anEnum : ProtocolMessageStatusEnum.values()){if (anEnum.value == value){return anEnum;}}return null;}
}
在使用时可以直接调用,并且多了自定义方法,getEnumByValue:
header.setStatus((byte) ProtocolMessageStatusEnum.OK.getValue());……eader.setSerializer((byte) ProtocolMessageSerializerEnum.getEnumByValue( …… );
当我们将枚举类应用在方法参数列表中,那就有很强的约束性。此外,如果对于一些方法只能接受数值,还可以通过 values 获取所有值,然后判断数值的正确性。
public class App {public static void main(String[] args) {setStatus(UserStatusEnum.NORMAL);}public static void setStatus(UserStatusEnum userStatus){// 判断参数是否为对于枚举实例if(isTrueValue(status)){User user = new User();user.setStatus(status);} else {Syetem.out.println("非法参数");}// 判断参数是否为对于枚举实例public static boolean isTrueValue(Integer num) {UserStatusEnum[] values = UserStatusEnum.values();for (UserStatusEnum statusEnum : values) {Integer value = statusEnum.getValue();if (value.equals(num)) {return true;}}return false;}
}