1. 【強制】避免通過一個類的對象引用訪問此類的靜態(tài)變量或靜態(tài)方法紊扬,無謂增加編譯器解析成本,直接用 類名來訪問即可固棚。
2. 【強制】所有的覆寫方法都弹,必須加 @Override
注解。
說明:getObject()
與 get0bject()
的問題锣杂。一個是字母的 O脂倦,一個是數(shù)字的 0,加 @Override
可以準確判斷是否覆蓋 成功元莫。另外赖阻,如果在抽象類中對方法簽名進行修改,其實現(xiàn)類會馬上編譯報錯踱蠢。
3. 【強制】相同參數(shù)類型火欧,相同業(yè)務含義棋电,才可以使用的可變參數(shù),參數(shù)類型避免定義為 Object苇侵。
說明:可變參數(shù)必須放置在參數(shù)列表的最后赶盔。(建議開發(fā)者盡量不用可變參數(shù)編程)
正例:public List<User> listUsers(String type, Long... ids) {...}
4. 【強制】外部正在調用的接口或者二方庫依賴的接口,不允許修改方法簽名榆浓,避免對接口調用方產生影 響于未。接口過時必須加 @Deprecated
注解,并清晰地說明采用的新接口或者新服務是什么陡鹃。
- 設計時沒有考慮周全烘浦,需要改造接口,需要通過增加新接口萍鲸,遷移后下線老接口的方式實現(xiàn)闷叉。
- REST接口只能增加參數(shù),不能減少參數(shù)脊阴,返回值的內容也是只增不減握侧。
5. 【強制】不能使用過時的類或方法。
說明:java.net.URLDecoder
中的方法 decode(String encodeStr)
這個方法已經過時蹬叭,應該使用雙參數(shù) decode(String source, String encode)
藕咏。接口提供方既然明確是過時接口,那么有義務同時提供新的接口秽五;作為調用方來說孽查,有義務去考證過時方法的新實現(xiàn)是什么。
6. 【強制】Object 的 equals 方法容易拋空指針異常坦喘,應使用常量或確定有值的對象來調用 equals盲再。
正例:"test".equals(param);
反例:param.equals("test");
說明:推薦使用 JDK7 引入的工具類 java.util.Objects#equals(Object a, Object b)
Java7 源碼如下所示:
public static boolean equals(Object a, Object b) {
return (a == b) || (a != null && a.equals(b));
}
7.【強制】所有整型包裝類對象之間值的比較,全部使用 equals 方法比較瓣铣。
說明:對于 Integer var = ?
在 -128 至 127 之間的賦值答朋,Integer 對象是在 IntegerCache.cache
產生,會復用已有對 象棠笑,這個區(qū)間內的 Integer 值可以直接使用 ==
進行判斷梦碗,但是這個區(qū)間之外的所有數(shù)據(jù),都會在堆上產生蓖救,并不會復 用已有對象洪规,這是一個大坑,推薦使用 equals 方法進行判斷循捺。
我的批注:相信有經驗的開發(fā)者都應該知道字符串 String 比較肯定用的是 equals斩例。
Java 世界里相等請用equals方法,==
表示對象相等从橘,一般在框架開發(fā)中會用到念赶。
8. 【強制】任何貨幣金額础钠,均以最小貨幣單位且為整型類型進行存儲。
9. 【強制】浮點數(shù)之間的等值判斷叉谜,基本數(shù)據(jù)類型不能使用 == 進行比較旗吁,包裝數(shù)據(jù)類型不能使用 equals 進行判斷。
說明:浮點數(shù)采用“尾數(shù)+階碼”的編碼方式停局,類似于科學計數(shù)法的“有效數(shù)字+指數(shù)”的表示方式阵漏。二進制無法精確表示大部分的十進制小數(shù),具體原理參考《碼出高效》翻具。
正例: (1)指定一個誤差范圍,兩個浮點數(shù)的差值在此范圍之內回还,則認為是相等的裆泳。
float a = 1.0F - 0.9F;
float b = 0.9F - 0.8F;
float diff = 1e-6F;
if (Math.abs(a - b) < diff) {
System.out.println("true");
}
(2)使用 BigDecimal 來定義值,再進行浮點數(shù)的運算操作柠硕。
BigDecimal a = new BigDecimal("1.0");
BigDecimal b = new BigDecimal("0.9");
BigDecimal c = new BigDecimal("0.8");
BigDecimal x = a.subtract(b);
BigDecimal y = b.subtract(c);
if (x.compareTo(y) == 0) {
System.out.println("true");
}
10. 【強制】BigDecimal 的等值比較應使用 compareTo()
方法工禾,而不是 equals()
方法。
說明:equals()
方法會比較值和精度(1.0 與 1.00 返回結果為 false)蝗柔,而 compareTo()
則會忽略精度闻葵。
11. 【強制】定義數(shù)據(jù)對象 DO 類時,屬性類型要與數(shù)據(jù)庫字段類型相匹配癣丧。
正例:數(shù)據(jù)庫字段的 bigint 必須與類屬性的 Long 類型相對應槽畔。 反例:某業(yè)務的數(shù)據(jù)庫表 id 字段定義類型為 bigint unsigned,實際類對象屬性為 Integer胁编,隨著 id 越來越大厢钧, 超過 Integer 的表示范圍而溢出成為負數(shù),此時數(shù)據(jù)庫 id 不支持存入負數(shù)拋出異常產生線上故障嬉橙。
12.【強制】禁止使用構造方法 BigDecimal(double)
的方式把 double 值轉化為 BigDecimal 對象早直。
說明:BigDecimal(double) 存在精度損失風險,在精確計算或值比較的場景中可能會導致業(yè)務邏輯異常市框。 如: BigDecimal g = new BigDecimal(0.1F)霞扬;
實際的存儲值為:0.100000001490116119384765625
正例:優(yōu)先推薦入參為 String 的構造方法,或使用 BigDecimal 的 valueOf 方法枫振,此方法內部其實執(zhí)行了 Double 的 toString喻圃,而 Double 的 toString 按 double 的實際能表達的精度對尾數(shù)進行了截斷。
BigDecimal recommend1 = new BigDecimal("0.1");
BigDecimal recommend2 = BigDecimal.valueOf(0.1);
13. 關于基本數(shù)據(jù)類型與包裝數(shù)據(jù)類型的使用標準如下:
1)【強制】所有的 POJO 類屬性必須使用包裝數(shù)據(jù)類型蒋得。
2)【強制】RPC 方法的返回值和參數(shù)必須使用包裝數(shù)據(jù)類型级及。
3)【推薦】所有的局部變量使用基本數(shù)據(jù)類型。
說明:POJO 類屬性沒有初值是提醒使用者在需要使用時额衙,必須自己顯式地進行賦值饮焦,任何 NPE 問題怕吴,或者入庫檢查, 都由使用者來保證县踢。
正例:數(shù)據(jù)庫的查詢結果可能是 null转绷,因為自動拆箱,用基本數(shù)據(jù)類型接收有 NPE 風險硼啤。
反例:某業(yè)務的交易報表上顯示成交總額漲跌情況议经,即正負 x%郊尝,x 為基本數(shù)據(jù)類型掘宪,調用的 RPC 服務,調用不成功時道宅, 返回的是默認值嗓袱,頁面顯示為 0%籍救,這是不合理的,應該顯示成中劃線-渠抹。所以包裝數(shù)據(jù)類型的 null 值蝙昙,能夠表示額外的 信息,如:遠程調用失敗梧却,異常退出奇颠。
14. 【強制】定義 DO / PO / DTO / VO 等 POJO 類時,不要設定任何屬性默認值放航。
反例:某業(yè)務的 DO 的 createTime 默認值為 new Date()烈拒;但是這個屬性在數(shù)據(jù)提取時并沒有置入具體值,在更新其它字段時又附帶更新了此字段三椿,導致創(chuàng)建時間被修改成當前時間缺菌。
15.【強制】序列化類新增屬性時,請不要修改 serialVersionUID 字段搜锰,避免反序列失敯橛簟;如果完全不兼容升級蛋叼,避免反序列化混亂焊傅,那么請修改 serialVersionUID 值。
說明:注意 serialVersionUID 不一致會拋出序列化運行時異常狈涮。
16.【強制】構造方法里面禁止加入任何業(yè)務邏輯狐胎,如果有初始化邏輯,請放在 init 方法中歌馍。
17.【強制】POJO 類必須寫 toString 方法握巢。使用 IDE 中的工具 source > generate toString 時,如果繼 承了另一個 POJO 類松却,注意在前面加一下 super.toString()
暴浦。
說明:在方法執(zhí)行拋出異常時溅话,可以直接調用 POJO 的 toString() 方法打印其屬性值,便于排查問題歌焦。
18.【強制】禁止在 POJO 類中飞几,同時存在對應屬性 xxx 的 isXxx() 和 getXxx() 方法。
說明:框架在調用屬性 xxx 的提取方法時独撇,并不能確定哪個方法一定是被優(yōu)先調用到屑墨,神坑之一。
19. 【推薦】使用索引訪問用 String 的 split 方法得到的數(shù)組時纷铣,需做最后一個分隔符后有無內容的檢查卵史, 否則會有拋 IndexOutOfBoundsException
的風險。
說明:
String str = "a,b,c,,";
String[] ary = str.split(",");
// 預期大于 3搜立,結果等于 3
System.out.println(ary.length);
20. 【推薦】當一個類有多個構造方法程腹,或者多個同名方法,這些方法應該按順序放置在一起儒拂,便于閱讀, 此條規(guī)則優(yōu)先于下一條色鸳。
正例:
public int method(int param);
protected double method(int param1, int param2);
private void method();
21. 【推薦】類內方法定義的順序依次是:公有方法或保護方法 > 私有方法 > getter / setter 方法社痛。
說明:公有方法是類的調用者和維護者最關心的方法,首屏展示最好命雀;保護方法雖然只是子類關心蒜哀,也可能是“模板設 計模式”下的核心方法;而私有方法外部一般不需要特別關心吏砂,是一個黑盒實現(xiàn)撵儿;因為承載的信息價值較低,所有 Service 和 DAO 的 getter / setter 方法放在類體最后狐血。
22. 【推薦】setter 方法中淀歇,參數(shù)名稱與類成員變量名稱一致,this.成員名=參數(shù)名匈织。在 getter / setter 方法中浪默,不要增加業(yè)務邏輯,增加排查問題的難度缀匕。
反例:
public Integer getData() {
if (condition) {
return this.data + 100;
} else {
return this.data - 100;
}
}
23. 【推薦】循環(huán)體內纳决,字符串的連接方式,使用 StringBuilder 的 append 方法進行擴展乡小。
反例:
String str = "start";
for (int i = 0; i < 100; i++) {
str = str + "hello";
}
說明:反編譯出的字節(jié)碼文件顯示每次循環(huán)都會 new 出一個 StringBuilder 對象阔加,然后進行 append 操作,最后通過 toString() 返回 String 對象满钟,造成內存資源浪費胜榔。
24. 【推薦】final 可以聲明類胳喷、成員變量、方法苗分、以及本地變量厌蔽,下列情況使用 final 關鍵字:
1)不允許被繼承的類,如:String 類摔癣。
2)不允許修改引用的域對象奴饮,如:POJO 類的域變量。
3)不允許被覆寫的方法择浊,如:POJO 類的 setter 方法戴卜。
4)不允許運行過程中重新賦值的局部變量。
5)避免上下文重復使用一個變量琢岩,使用 final 關鍵字可以強制重新定義一個變量投剥,方便更好地進行重構。
25. 【推薦】慎用 Object 的 clone 方法來拷貝對象担孔。
說明:對象 clone 方法默認是淺拷貝江锨,若想實現(xiàn)深拷貝需覆寫 clone 方法實現(xiàn)域對象的深度遍歷式拷貝。
26. 【推薦】類成員與方法訪問控制從嚴:
1)如果不允許外部直接通過 new 來創(chuàng)建對象糕篇,那么構造方法必須是 private啄育。
2)工具類不允許有 public 或 default 構造方法。
3)類非 static 成員變量并且與子類共享拌消,必須是 protected挑豌。
4)類非 static 成員變量并且僅在本類使用,必須是 private墩崩。
5)類 static 成員變量如果僅在本類使用氓英,必須是 private。
6)若是 static 成員變量鹦筹,考慮是否為 final铝阐。
7)類成員方法只供類內部調用,必須是 private铐拐。
8)類成員方法只對繼承類公開饰迹,那么限制為 protected。 說明:任何類余舶、方法啊鸭、參數(shù)、變量匿值,嚴控訪問范圍赠制。過于寬泛的訪問范圍,不利于模塊解耦。思考:如果是一個 private 的方法钟些,想刪除就刪除烟号,可是一個 public 的 service 成員方法或成員變量,刪除一下政恍,不得手心冒點汗嗎汪拥? 變量像自己的小孩,盡量在自己的視線內篙耗,變量作用域太大迫筑,無限制的到處跑,那么你會擔心的宗弯。
補充知識
六大設計原則解讀
- 單一職責原則(Single Responsibility Principle)脯燃,簡稱是 SRP
SRP 的原話解釋是:
There should never be more than one reason for a class to change.
應該有且僅有一個原因引起類的變更。
2. 里氏替換原則(Liskov Substitution Principle)
Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it.
所有引用基類的地方必須能透明地使用其子類的對象蒙保。
- 子類必須完全實現(xiàn)父類的方法
- 子類可以有自己的個性
- 覆蓋或實現(xiàn)父類的方法時輸入參數(shù)可以被放大
- 覆寫或實現(xiàn)父類的方法時輸出結果可以被縮小
目的: 增強程序的健壯性辕棚,版本升級時也可以保持非常好的兼容性。即使增加子類邓厕,原有的子類還可以繼續(xù)運行逝嚎。在實際項目中,每個子類對應不同的業(yè)務含義详恼,使用父類作為參數(shù)懈糯,傳遞不同的子類完成不同的業(yè)務邏輯,非常完美单雾!
3. 依賴倒置原則(Dependence Inversion Principle)
原始定義:
High level modules should not depend upon low level modules.Both should depend upon abstractions.Abstractions should not depend upon details.Details should depend upon abstractions.
翻譯過來,包含三層含義:
? 高層模塊不應該依賴低層模塊她紫,兩者都應該依賴其抽象硅堆;
? 抽象不應該依賴細節(jié);
? 細節(jié)應該依賴抽象贿讹。
依賴倒置原則是6個設計原則中最難以實現(xiàn)的原則渐逃,它是實現(xiàn)開閉原則的重要途徑,依賴倒置原則沒有實現(xiàn)民褂,就別想實現(xiàn)對擴展開放茄菊,對修改關閉。在項目中赊堪,大家只要記住是“面向接口編程”就基本上抓住了依賴倒置原則的核心面殖。
4. 接口隔離原則(Interface Segregation Principle)
它有兩種定義,如下所示:
?Clients should not be forced to depend upon interfaces that they don't use.(客戶端不應該依賴它不需要的接口哭廉。)
?The dependency of one class to another one should depend on the smallest possible interface.(類間的依賴關系應該建立在最小的接口上脊僚。)
新事物的定義一般都比較難理解,晦澀難懂是正常的遵绰。我們把這兩個定義剖析一下辽幌,先說第一種定義:“客戶端不應該依賴它不需要的接口”增淹,那依賴什么?依賴它需要的接口乌企,客戶端需要什么接口就提供什么接口虑润,把不需要的接口剔除掉,那就需要對接口進行細化加酵,保證其純潔性拳喻;再看第二種定義:“類間的依賴關系應該建立在最小的接口上”,它要求是最小的接口虽画,也是要求接口細化舞蔽,接口純潔,與第一個定義如出一轍码撰,只是一個事物的兩種不同描述渗柿。
5. 迪米特法則(Law of Demeter)
迪米特法則(Law of Demeter,LoD)也稱為最少知識原則(Least Knowledge Principle,LKP),雖然名字不同脖岛,但描述的是同一個規(guī)則:一個對象應該對其他對象有最少的了解朵栖。
通俗地講,一個類應該對自己需要耦合或調用的類知道得最少柴梆,你(被耦合或調用的類)的內部是如何復雜都和我沒關系陨溅,那是你的事情,我就知道你提供的這么多public方法绍在,我就調用這么多门扇,其他的我一概不關心。
迪米特法則的核心觀念就是類間解耦偿渡,弱耦合臼寄,只有弱耦合了以后,類的復用率才可以提高溜宽。其要求的結果就是產生了大量的中轉或跳轉類吉拳,導致系統(tǒng)的復雜性提高,同時也為維護帶來了難度适揉。讀者在采用迪米特法則時需要反復權衡留攒,既做到讓結構清晰,又做到高內聚低耦合嫉嘀。
6. 開閉原則(Open Closed Principle)
Software entities like classes,modules and functions should be open for extension but closed for modifications.(一個軟件實體如類炼邀、模塊和函數(shù)應該對擴展開放,對修改關閉剪侮。)
開閉原則告訴我們應盡量通過擴展軟件實體的行為來實現(xiàn)變化汤善,而不是通過修改已有的代碼來完成變化,它是為軟件實體的未來事件而制定的對現(xiàn)行開發(fā)設計進行約束的一個原則。
把這6個原則的首字母(里氏替換原則和迪米特法則的首字母重復红淡,只取一個)聯(lián)合起來就是SOLID(solid不狮,穩(wěn)定的),其代表的含義也就是把這6個原則結合使用的好處:建立穩(wěn)定在旱、靈活摇零、健壯的設計,而開閉原則又是重中之重桶蝎,是最基礎的原則驻仅,是其他5大原則的精神領袖。
參考(References)
- 《1. 2022 Java開發(fā)手冊(黃山版).pdf
- 《設計模式之禪 第1版》
- 《Java技術手冊 第6版》
- 《編寫高質量代碼:改善Java程序的151個建議》
- 白話阿里巴巴Java開發(fā)手冊(安全規(guī)約) - 李艷鵬 - 簡書(http://www.reibang.com/p/9528c4ea1504)