常量
常量其实没什么可说的, 只要注意两点. 一是需要甄别是否是真正的常量, 并不是所有的 static final 都是常量. 二是不要把所有常量都集中写在一个类里, 而是根据实际关系, 有组织的放到各个包甚至各个类中. 如果写的规矩一点, 常量类应像工具类一样, 类添加 final 关键字, 同时私有化构造方法, 防止被继承和实例化. 示例如下:
public final class Constant {
private Constant() {}
public static final String SUCCESS = "success";
public static final String FAILED = "failed";
}
枚举
当前项目中经常看到的这样形式的枚举类: 带有两个字段, 一个字段为整型, 表示枚举值, 一个字段为字符串, 表示枚举值的描述. 常见写法如下:
public enum AttachmentEnum {
COMPLETE(1, "完整"),
NON_COMPLETE(0, "不完整");
private final Integer value;
private final String name;
AttachmentEnum(Integer value, String name) {
this.value = value;
this.name = name;
}
public int getValue() {
return value;
}
public String getName() {
return name;
}
public static AttachmentEnum of(int value) {
for (AttachmentEnum o : AttachmentEnum.values()) {
if (value == o.getValue()) {
return o;
}
}
return null;
}
public static String getName(Integer value) {
if (value == null) {
return null;
}
for (AttachmentEnum o : AttachmentEnum.values()) {
if (value == o.getValue()) {
return o.getName();
}
}
return null;
}
public static Integer getValueByName(String name) {
AttachmentEnum[] attachmentEnums = AttachmentEnum.values();
for (AttachmentEnum value : attachmentEnums) {
if (value.getName().equals(name)) {
return value.getValue();
}
}
return null;
}
}
其中每个枚举类都有根据值获取描述和根据描述获取值的方法, 每次写一个枚举类都要复制粘贴修改一份类似代码. 此类重复代码臃肿而无必要, 其实可利用反射写一个工具类, 用于通用处理所有这样的枚举类.
import org.apache.commons.lang.StringUtils;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
/**
* 枚举工具类.
* <p> 用于带有一个 Integer 和 String 的枚举类
* <p> 可根据值获取名称, 或根据名称获取值
*/
public final class ValueNameEnumUtils {
private ValueNameEnumUtils() {
}
/**
* 判断给定值是否在枚举类选项的值.
* <p> 如无整型字段, 抛出异常
* <p> 如有多个整型字段, 使用第一个
*/
public static <E extends Enum<E>> boolean valueOfEnum(final Class<E> enumClass, int value) {
String fieldName = getFirstIntegerField(enumClass).getName();
String methodName = getGetterName(fieldName);
try {
Method getter = enumClass.getMethod(methodName);
for (E e : Arrays.asList(enumClass.getEnumConstants())) {
if (value == (Integer) getter.invoke(e)) {
return true;
}
}
} catch (NoSuchMethodException e) {
throw new RuntimeException("枚举类字段无取值方法: " + enumClass.getSimpleName() + "." + fieldName);
} catch (IllegalAccessException e) {
throw new RuntimeException("枚举类字段取值方法无法访问: " + enumClass.getSimpleName());
} catch (InvocationTargetException e) {
throw new RuntimeException("枚举类字段取值方法调用失败: " + enumClass.getSimpleName());
}
return false;
}
/**
* 返回给定整型值的对应的枚举类.
* <p> 如无整型字段, 抛出异常
* <p> 如值对应多个枚举, 使用第一个
*/
public static <E extends Enum<E>> E getByValue(final Class<E> enumClass, int value) {
String fieldName = getFirstIntegerField(enumClass).getName();
String methodName = getGetterName(fieldName);
try {
Method getter = enumClass.getMethod(methodName);
for (E e : Arrays.asList(enumClass.getEnumConstants())) {
if (value == (Integer) getter.invoke(e)) {
return e;
}
}
} catch (NoSuchMethodException e) {
throw new RuntimeException("枚举类字段无取值方法: " + enumClass.getSimpleName() + "." + fieldName);
} catch (IllegalAccessException e) {
throw new RuntimeException("枚举类字段取值方法无法访问: " + enumClass.getSimpleName());
} catch (InvocationTargetException e) {
throw new RuntimeException("枚举类字段取值方法调用失败: " + enumClass.getSimpleName());
}
return null;
}
/**
* 返回给字符串的对应的枚举类.
* <p> 如无字符串字段, 抛出异常
* <p> 如字符串对应多个枚举, 使用第一个
*/
public static <E extends Enum<E>> E getByName(final Class<E> enumClass, String name) {
String fieldName = getFirstStringField(enumClass).getName();
String methodName = getGetterName(fieldName);
try {
Method getter = enumClass.getMethod(methodName);
for (E e : Arrays.asList(enumClass.getEnumConstants())) {
if (name != null && name.equals((String) getter.invoke(e))) {
return e;
}
}
} catch (NoSuchMethodException e) {
throw new RuntimeException("枚举类字段无取值方法: " + enumClass.getSimpleName() + "." + fieldName);
} catch (IllegalAccessException e) {
throw new RuntimeException("枚举类字段取值方法无法访问: " + enumClass.getSimpleName());
} catch (InvocationTargetException e) {
throw new RuntimeException("枚举类字段取值方法调用失败: " + enumClass.getSimpleName());
}
return null;
}
private static <E extends Enum<E>> Field getFirstIntegerField(final Class<E> enumClass) {
for (Field field : enumClass.getDeclaredFields()) {
if (field.getType().getName().equals("int") || field.getType().getName().equals("java.lang.Integer")) {
return field;
}
}
throw new IllegalStateException("枚举类无数值字段: " + enumClass.getSimpleName());
}
private static <E extends Enum<E>> Field getFirstStringField(final Class<E> enumClass) {
for (Field field : enumClass.getDeclaredFields()) {
if (field.getType().getName().equals("java.lang.String")) {
return field;
}
}
throw new IllegalStateException("枚举类无字符串字段: " + enumClass.getSimpleName());
}
private static String getGetterName(String fieldName) {
return StringUtils.isEmpty(fieldName) ? "" : "get" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
}
}
上述代码没有什么好讲, 跟反射获取对象中字段, 并调用对应 getter 方法基本一样. 唯一需要注意的点是枚举泛型的写法<E extends Enum<E>>
.
同时, 利用 lombok 提供的注解, 这类枚举代码可简化如下:
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public enum Attachment {
COMPLETE(1, "完整"),
NON_COMPLETE(0, "不完整");
private final Integer value;
private final String name;
}
这样写枚举, 看上去是不是简洁清爽了很多? 再写新的枚举, 只需要关注枚举项即可.
除此之外, 写枚举还有一些小经验或注意事项. 首先是命名, 可以不加后面的 "Enum", 通常凡是叫 XXXStatus 或者 XXXType 的, 基本上一望可知, 不是常量就是枚举, 而且 Java 是强类型语言, 用名称表明类型多少有些啰嗦. 其次, 如果枚举中的数值是自己设置, 最好不要设成 1, 2, 3 而是最好设置成 10, 20, 30 或者 100, 200, 300; 这样方便后续扩展, 可以在中间添加枚举项. 而且同一类枚举, 值设置在各系统中最好统一, 比如审核状态, 均设置 100 为审核通过, -100 为审核不通过, 这样可以加快不同项目代码的上手速度.
枚举常见的使用错误主要在比较判断上. 一种是取 value 值 Integer, 不使用 equals 而用 == 判断. 另一种是使用了 equals, 却不小心拿枚举类型和整型比较. 前者属于基本类型与包装类型不分. 由于整型池原理, 枚举 value 值设置在 127 以内, 估计一时都察觉不到问题. 一般都要吃过一次大亏, 上过一次大当之后, 才能增长此类经验教训. 上文中摘抄的代码其实就有这个问题. 后一种错误多是无心之失, 代码写快了总是难易避免. 不过, 如果养成良好习惯, 提交代码之前, 使用 findbug 插件扫描一下, 这两种错误就能轻松发现了.