讀書,收獲,分享
建議后面的五角星僅代表筆者個(gè)人需要注意的程度。
Talk is cheap.Show me the code
建議83:推薦使用枚舉定義常量★★☆☆☆
-
枚舉常量更簡(jiǎn)單
枚舉常量只需要定義每個(gè)枚舉項(xiàng),不需要定義枚舉值
-
枚舉常量屬于穩(wěn)態(tài)型
這也是我們最看重枚舉的地方:在編譯期間限定類型,不允許發(fā)生越界的情況。
-
枚舉具有內(nèi)置方法
通過(guò)
values方法即可獲得所有的枚舉項(xiàng),得益于枚舉內(nèi)置的方法,每個(gè)枚舉都是java.lang.Enum的子類,該基類提供了諸如獲得排序值的ordinal方法、compareTo比較方法等,大大簡(jiǎn)化了常量的訪問(wèn)。 -
枚舉可以自定義方法
枚舉常量不僅可以定義靜態(tài)方法,還可以定義非靜態(tài)方法,而且還能夠從根本上杜絕常量類被實(shí)例化。
枚舉類型是不能有繼承的,也就是說(shuō)一個(gè)枚舉常量定義完畢后,除非修改重構(gòu),否則無(wú)法做擴(kuò)展
注意:在項(xiàng)目開發(fā)中,推薦使用枚舉常量代替接口常量或類常量。
建議84:使用構(gòu)造函數(shù)協(xié)助描述枚舉項(xiàng)★☆☆☆☆
推薦在枚舉定義中為每個(gè)枚舉項(xiàng)定義描述,特別是在大規(guī)模的項(xiàng)目開發(fā)中,大量的常量項(xiàng)定義使用枚舉項(xiàng)描述比在接口常量或類常量中增加注釋的方式友好得多,簡(jiǎn)潔得多。如下:
public enum OrderStateEnum {
ORDER_PAY_SUCC(1,"訂單支付成功");
private Integer code;
private String name;
OrderStateEnum(Integer code, String name) {
this.code = code;
this.name = name;
}
}
建議85:小心switch帶來(lái)的空值異常★★☆☆☆
示例如下:
public static void main(String[] args) {
orderUpt(null);
}
public static void orderUpt(OrderStateEnum orderStateEnum){
switch (orderStateEnum){
case ORDER_PAY_SUCC:
System.out.println("訂單支付成功,執(zhí)行后續(xù)操作");
break;
default:
System.out.println("未匹配到訂單狀態(tài),無(wú)操作");
}
}
運(yùn)行結(jié)果:
Exception in thread "main" java.lang.NullPointerException
at com.jyswm.demo.Client.orderUpt(Client.java:13)
at com.jyswm.demo.Client.main(Client.java:10)
因?yàn)榫幾g時(shí),編譯器判斷出switch語(yǔ)句后的參數(shù)是枚舉類型,然后就會(huì)根據(jù)枚舉的排序值繼續(xù)匹配,也就是說(shuō)上面的代碼與以下代碼相同:
public static void orderUpt(OrderStateEnum orderStateEnum){
switch (orderStateEnum.ordinal()){
case OrderStateEnum.ORDER_PAY_SUCC.ordinal():
break;
default:
System.out.println("未匹配到訂單狀態(tài),無(wú)操作");
}
}
//switch語(yǔ)句是先計(jì)算orderStateEnum變量的排序值,然后與枚舉常量的每個(gè)排序值進(jìn)行對(duì)比的。
//在我們的例子中orderStateEnum變量是null值,無(wú)法執(zhí)行ordinal方法,于是報(bào)空指針異常了
建議86:在switch的default代碼塊中增加AssertionError錯(cuò)誤★☆☆☆☆
在default后直接拋出一個(gè)AssertionError錯(cuò)誤,其含義就是“不要跑到這里來(lái),一跑到這里就會(huì)出問(wèn)題",這樣可以保證在增加一個(gè)枚舉項(xiàng)的情況下,若其他代碼未修改,運(yùn)行期馬上就會(huì)報(bào)錯(cuò),這樣一來(lái)就很容易查找到錯(cuò)誤,方便立刻排除。
建議87:使用valueOf前必須進(jìn)行校驗(yàn)★☆☆☆☆
通過(guò)枚舉的valueOf方法把String類型的名稱轉(zhuǎn)變?yōu)槊杜e項(xiàng),示例:
public static void main(String[] args) {
//注意此處的"ORDER_PAY_SU"在枚舉中是不存在的
OrderStateEnum orderStateEnum = OrderStateEnum.valueOf("ORDER_PAY_SU");
}
運(yùn)行結(jié)果:
Exception in thread "main" java.lang.IllegalArgumentException: No enum constant com.jyswm.demo.OrderStateEnum.ORDER_PAY_SU
at java.lang.Enum.valueOf(Enum.java:238)
at com.jyswm.demo.OrderStateEnum.valueOf(OrderStateEnum.java:3)
at com.jyswm.demo.Client.main(Client.java:11)
//這與我們的習(xí)慣用法非常不一致,例如我們從一個(gè)List中查找一個(gè)元素,即使不存在也不會(huì)報(bào)錯(cuò),頂多indexOf方法返回-1
valueOf方法的源代碼:
//由于valueOf(String name)方法是不可見的,是JVM內(nèi)置的方法
//下面是通過(guò)閱讀公開的valueOf方法來(lái)模擬其運(yùn)行原理
public static <T extends Enum<T>> T valueOf(Class<T> enumType, String name) {
//通過(guò)反射,從常量列表中查找
T result = enumType.enumConstantDirectory().get(name);
if (result != null)
return result;
if (name == null)
throw new NullPointerException("Name is null");
//最后拋出無(wú)效參數(shù)異常
throw new IllegalArgumentException("No enum const " + enumType + "." + name);
}
注意:使用
valueOf前必須進(jìn)行校驗(yàn)
建議88:用枚舉實(shí)現(xiàn)工廠方法模式更簡(jiǎn)潔 ★☆☆☆☆
為什么要使用枚舉類型的工廠方法模式呢?有以下三個(gè)優(yōu)點(diǎn):
- 避免錯(cuò)誤調(diào)用的發(fā)生
- 性能好,使用便捷
- 降低類間耦合
注意:下一次,使用枚舉來(lái)實(shí)現(xiàn)工廠方法模式。
建議89:枚舉項(xiàng)的數(shù)量限制在64個(gè)以內(nèi) ★☆☆☆☆
示例代碼:
public static void main(String[] args) {
//創(chuàng)建包含所有枚舉項(xiàng)的EnumSet(為什么強(qiáng)調(diào)64呢?)
EnumSet enumSet = EnumSet.allOf(OrderStateEnum.class);
}
allOf源代碼:
public static <E extends Enum<E>> EnumSet<E> allOf(Class<E> elementType) {
EnumSet<E> result = noneOf(elementType);
result.addAll();
return result;
}
public static <E extends Enum<E>> EnumSet<E> noneOf(Class<E> elementType) {
Enum<?>[] universe = getUniverse(elementType);
if (universe == null)
throw new ClassCastException(elementType + " not an enum");
if (universe.length <= 64)
return new RegularEnumSet<>(elementType, universe);
else
return new JumboEnumSet<>(elementType, universe);
}
java是這樣處理的,當(dāng)枚舉項(xiàng)數(shù)量小于等于64時(shí),創(chuàng)建一個(gè)RegularEnumSet實(shí)例對(duì)象,大于64時(shí)則創(chuàng)建一個(gè)JumboEnumSet實(shí)例對(duì)象。
為什么要如此處理呢?看看這兩個(gè)類之間的差異,源代碼如下:
RegularEnumSet類:
class RegularEnumSet<E extends Enum<E>> extends EnumSet<E> {
//記錄所有枚舉排序號(hào),注意是long型
private long elements = 0L;
//構(gòu)造函數(shù)
RegularEnumSet(Class<E>elementType, Enum<?>[] universe) {
super(elementType, universe);
}
//加入所有元素
void addAll() {
if (universe.length != 0)
elements = -1L >>> -universe.length;
}
/***省略其他代碼***/
}
我們知道枚舉項(xiàng)的排序值ordinal是從0、1、2……依次遞增的,沒(méi)有重號(hào),沒(méi)有跳號(hào),RegularEnumSet就是利用這一點(diǎn)把每個(gè)枚舉項(xiàng)的ordinal映射到一個(gè)long類型的每個(gè)位,簡(jiǎn)單地說(shuō),Java把一個(gè)不多于64個(gè)枚舉項(xiàng)的枚舉映射到了一個(gè)long類型變量上。效率和性能肯定是非常優(yōu)秀。
JumboEnumSet類:
class JumboEnumSet<E extends Enum<E>> extends EnumSet<E> {
//映射所有的枚舉項(xiàng)
private long elements[];
//構(gòu)造函數(shù)
JumboEnumSet(Class<E>elementType, Enum<?>[] universe) {
super(elementType, universe);
//默認(rèn)長(zhǎng)度是枚舉項(xiàng)數(shù)量除以64再加1
elements = new long[(universe.length + 63) >>> 6];
}
void addAll() elements{
//elements中每個(gè)元素表示64個(gè)枚舉項(xiàng)
for (int i = 0; i < elements.length; i++)
elements[i] = -1;
elements[elements.length - 1] >>>= -universe.length;
size = universe.length;
}
JumboEnumSet類把枚舉項(xiàng)按照64個(gè)元素一組拆分成了多組,每組都映射到一個(gè)long類型的數(shù)字上,然后該數(shù)組再放置到elements數(shù)組中。簡(jiǎn)單來(lái)說(shuō)JumboEnumSet類的原理與RegularEnumSet相似,只是JumboEnumSet使用了long數(shù)組容納更多的枚舉項(xiàng)。
注意:枚舉項(xiàng)數(shù)量不要超過(guò)64,否則建議拆分。
建議90:小心注解繼承★☆☆☆☆
@Inherited,它表示一個(gè)注解是否可以自動(dòng)被繼承
采用@Inherited元注解有利有弊,利的地方是一個(gè)注解只要標(biāo)注到父類,所有的子類都會(huì)自動(dòng)具有與父類相同的注解,整齊、統(tǒng)一而且便于管理,弊的地方是單單閱讀子類代碼,我們無(wú)從知道為何邏輯會(huì)被改變,因?yàn)樽宇悰](méi)有明顯標(biāo)注該注解??傮w上來(lái)說(shuō),使用@Inherited元注解的弊大于利,特別是一個(gè)類的繼承層次較深時(shí),如果注解較多,則很難判斷出是哪個(gè)注解對(duì)子類產(chǎn)生了邏輯劫持。
建議91:枚舉和注解結(jié)合使用威力更大★☆☆☆☆
訪問(wèn)控制列表設(shè)計(jì)案例,示例如下:
//鑒權(quán)者接口
interface Identifier {
//無(wú)權(quán)訪問(wèn)時(shí)的禮貌語(yǔ)
String REFUSE_WORD = "您無(wú)權(quán)訪問(wèn)";
// 鑒權(quán)
public boolean identify();
}
//常用鑒權(quán)者
enum CommonIdentifier implements Identifier {
//權(quán)限級(jí)別
Reader, Author, Admin;
//實(shí)現(xiàn)鑒權(quán)
public boolean identify() {
return false;
}
}
//權(quán)限級(jí)別的注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface Access {
//確定什么級(jí)別可以訪問(wèn)
CommonIdentifier level() default CommonIdentifier.Admin;
}
//商業(yè)邏輯,默認(rèn)訪問(wèn)權(quán)限是Admin
@Access(level = CommonIdentifier.Author)
class Foo {
}
//模擬實(shí)現(xiàn)
public class Client {
public static void main(String[] args) {
//初始化商業(yè)邏輯
Foo b = new Foo();
//獲取注解
Access access = b.getClass().getAnnotation(Access.class);
//沒(méi)有Access注解或者鑒權(quán)失敗
if (access == null || !access.level().identify()) {
//沒(méi)有Access注解或者鑒權(quán)失敗
System.out.println(access.level().REFUSE_WORD);
}
}
}
建議92:注意@Override不同版本的區(qū)別★☆☆☆☆
在多環(huán)境部署應(yīng)用時(shí),需要考慮@Override在不同版本下代表的意義,例如Java 1.6版本的程序移植到1.5版本環(huán)境中,就需要?jiǎng)h除實(shí)現(xiàn)接口方法上的@Override注解。