常量
常量其實(shí)沒什么可說的, 只要注意兩點(diǎn). 一是需要甄別是否是真正的常量, 并不是所有的 static final 都是常量. 二是不要把所有常量都集中寫在一個(gè)類里, 而是根據(jù)實(shí)際關(guān)系, 有組織的放到各個(gè)包甚至各個(gè)類中. 如果寫的規(guī)矩一點(diǎn), 常量類應(yīng)像工具類一樣, 類添加 final 關(guān)鍵字, 同時(shí)私有化構(gòu)造方法, 防止被繼承和實(shí)例化. 示例如下:
public final class Constant {
private Constant() {}
public static final String SUCCESS = "success";
public static final String FAILED = "failed";
}
枚舉
當(dāng)前項(xiàng)目中經(jīng)常看到的這樣形式的枚舉類: 帶有兩個(gè)字段, 一個(gè)字段為整型, 表示枚舉值, 一個(gè)字段為字符串, 表示枚舉值的描述. 常見寫法如下:
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;
}
}
其中每個(gè)枚舉類都有根據(jù)值獲取描述和根據(jù)描述獲取值的方法, 每次寫一個(gè)枚舉類都要復(fù)制粘貼修改一份類似代碼. 此類重復(fù)代碼臃腫而無必要, 其實(shí)可利用反射寫一個(gè)工具類, 用于通用處理所有這樣的枚舉類.
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> 用于帶有一個(gè) Integer 和 String 的枚舉類
* <p> 可根據(jù)值獲取名稱, 或根據(jù)名稱獲取值
*/
public final class ValueNameEnumUtils {
private ValueNameEnumUtils() {
}
/**
* 判斷給定值是否在枚舉類選項(xiàng)的值.
* <p> 如無整型字段, 拋出異常
* <p> 如有多個(gè)整型字段, 使用第一個(gè)
*/
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("枚舉類字段取值方法調(diào)用失敗: " + enumClass.getSimpleName());
}
return false;
}
/**
* 返回給定整型值的對(duì)應(yīng)的枚舉類.
* <p> 如無整型字段, 拋出異常
* <p> 如值對(duì)應(yīng)多個(gè)枚舉, 使用第一個(gè)
*/
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("枚舉類字段取值方法調(diào)用失敗: " + enumClass.getSimpleName());
}
return null;
}
/**
* 返回給字符串的對(duì)應(yīng)的枚舉類.
* <p> 如無字符串字段, 拋出異常
* <p> 如字符串對(duì)應(yīng)多個(gè)枚舉, 使用第一個(gè)
*/
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("枚舉類字段取值方法調(diào)用失敗: " + 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("枚舉類無數(shù)值字段: " + 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);
}
}
上述代碼沒有什么好講, 跟反射獲取對(duì)象中字段, 并調(diào)用對(duì)應(yīng) getter 方法基本一樣. 唯一需要注意的點(diǎn)是枚舉泛型的寫法<E extends Enum<E>>.
同時(shí), 利用 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;
}
這樣寫枚舉, 看上去是不是簡潔清爽了很多? 再寫新的枚舉, 只需要關(guān)注枚舉項(xiàng)即可.
除此之外, 寫枚舉還有一些小經(jīng)驗(yàn)或注意事項(xiàng). 首先是命名, 可以不加后面的 "Enum", 通常凡是叫 XXXStatus 或者 XXXType 的, 基本上一望可知, 不是常量就是枚舉, 而且 Java 是強(qiáng)類型語言, 用名稱表明類型多少有些啰嗦. 其次, 如果枚舉中的數(shù)值是自己設(shè)置, 最好不要設(shè)成 1, 2, 3 而是最好設(shè)置成 10, 20, 30 或者 100, 200, 300; 這樣方便后續(xù)擴(kuò)展, 可以在中間添加枚舉項(xiàng). 而且同一類枚舉, 值設(shè)置在各系統(tǒng)中最好統(tǒng)一, 比如審核狀態(tài), 均設(shè)置 100 為審核通過, -100 為審核不通過, 這樣可以加快不同項(xiàng)目代碼的上手速度.
枚舉常見的使用錯(cuò)誤主要在比較判斷上. 一種是取 value 值 Integer, 不使用 equals 而用 == 判斷. 另一種是使用了 equals, 卻不小心拿枚舉類型和整型比較. 前者屬于基本類型與包裝類型不分. 由于整型池原理, 枚舉 value 值設(shè)置在 127 以內(nèi), 估計(jì)一時(shí)都察覺不到問題. 一般都要吃過一次大虧, 上過一次大當(dāng)之后, 才能增長此類經(jīng)驗(yàn)教訓(xùn). 上文中摘抄的代碼其實(shí)就有這個(gè)問題. 后一種錯(cuò)誤多是無心之失, 代碼寫快了總是難易避免. 不過, 如果養(yǎng)成良好習(xí)慣, 提交代碼之前, 使用 findbug 插件掃描一下, 這兩種錯(cuò)誤就能輕松發(fā)現(xiàn)了.