\1. 【強制】避免通過一個類的對象引用訪問此類的靜態(tài)變量或靜態(tài)方法,無謂增加編譯器解析成本,直接用類名來訪問即可。
\2. 【強制】所有的覆寫方法,必須加@Override
注解。
說明:getObject()與 get0bject()的問題。一個是字母的 O,一個是數(shù)字的 0,加@Override 可以準確判斷是否覆蓋成功。另外,如果在抽象類中對方法簽名進行修改,其實現(xiàn)類會馬上編譯報錯。
\3. 【強制】相同參數(shù)類型,相同業(yè)務含義,才可以使用 Java 的可變參數(shù),避免使用 Object。
說明:可變參數(shù)必須放置在參數(shù)列表的最后。(建議開發(fā)者盡量不用可變參數(shù)編程)
正例:public List<User> listUsers(String type, Long... ids) {...}
\4. 【強制】外部正在調(diào)用或者二方庫依賴的接口,不允許修改方法簽名,避免對接口調(diào)用方產(chǎn)生影響。接口過時必須加@Deprecated 注解,并清晰地說明采用的新接口或者新服務是什么。
\5. 【強制】不能使用過時的類或方法。
說明:java.net.URLDecoder 中的方法 decode(String encodeStr) 這個方法已經(jīng)過時,應該使用雙參數(shù)decode(String source, String encode)。接口提供方既然明確是過時接口,那么有義務同時提供新的接口;作為調(diào)用方來說,有義務去考證過時方法的新實現(xiàn)是什么。
\6. 【強制】Object
的 equals
方法容易拋空指針異常,應使用常量或確定有值的對象來調(diào)用 equals
。
正例:"test".equals(object);
反例:object.equals("test");
說明:推薦使用 JDK7 引入的工具類 java.util.Objects#equals(Object a, Object b)
\7. 【強制】所有整型包裝類對象之間值的比較,全部使用 equals
方法比較。
說明:對于 Integer var = ? 在-128 至 127 之間的賦值,Integer 對象是在 IntegerCache.cache 產(chǎn)生,會復用已有對象,這個區(qū)間內(nèi)的 Integer 值可以直接使用==進行判斷,但是這個區(qū)間之外的所有數(shù)據(jù),都會在堆上產(chǎn)生,并不會復用已有對象,這是一個大坑,推薦使用 equals 方法進行判斷。
\8. 【強制】任何貨幣金額,均以最小貨幣單位且整型類型來進行存儲。
\9. 【強制】浮點數(shù)之間的等值判斷,基本數(shù)據(jù)類型不能用==來比較,包裝數(shù)據(jù)類型不能用 equals
來判斷。
說明:浮點數(shù)采用“尾數(shù)+階碼”的編碼方式,類似于科學計數(shù)法的“有效數(shù)字+指數(shù)”的表示方式。二進制無法精確表示大部分的十進制小數(shù),具體原理參考《碼出高效》。
反例:
float a = 1.0F - 0.9F;
float b = 0.9F - 0.8F;
if (a == b) {
// 預期進入此代碼塊,執(zhí)行其它業(yè)務邏輯
// 但事實上 a==b 的結(jié)果為 false
}
Float x = Float.valueOf(a);
Float y = Float.valueOf(b);
if (x.equals(y)) {
// 預期進入此代碼塊,執(zhí)行其它業(yè)務邏輯
// 但事實上 equals 的結(jié)果為 false
}
正例:
(1) 指定一個誤差范圍,兩個浮點數(shù)的差值在此范圍之內(nèi),則認為是相等的。
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 返回結(jié)果為 false),而 compareTo()則會忽略精度。
\11.【強制】定義數(shù)據(jù)對象 DO
類時,屬性類型要與數(shù)據(jù)庫字段類型相匹配。
正例:數(shù)據(jù)庫字段的 bigint 必須與類屬性的 Long 類型相對應。
反例:某個案例的數(shù)據(jù)庫表 id 字段定義類型 bigint unsigned,實際類對象屬性為 Integer,隨著 id 越來越大,超過 Integer 的表示范圍而溢出成為負數(shù)。
\12.【強制】禁止使用構(gòu)造方法 BigDecimal(double)
的方式把 double
值轉(zhuǎn)化為 BigDecimal
對象。
說明:BigDecimal(double)存在精度損失風險,在精確計算或值比較的場景中可能會導致業(yè)務邏輯異常。如:BigDecimal g = new BigDecimal(0.1F); 實際的存儲值為:0.10000000149
正例:優(yōu)先推薦入?yún)?String 的構(gòu)造方法,或使用 BigDecimal 的 valueOf 方法,此方法內(nèi)部其實執(zhí)行了
Double 的 toString,而 Double 的 toString 按 double 的實際能表達的精度對尾數(shù)進行了截斷。
BigDecimal recommend1 = new BigDecimal("0.1");
BigDecimal recommend2 = BigDecimal.valueOf(0.1);
\13.關(guān)于基本數(shù)據(jù)類型與包裝數(shù)據(jù)類型的使用標準如下:
? 1) 【強制】所有的 POJO
類屬性必須使用包裝數(shù)據(jù)類型。
? 2) 【強制】RPC
方法的返回值和參數(shù)必須使用包裝數(shù)據(jù)類型。
? 3) 【推薦】所有的局部變量使用基本數(shù)據(jù)類型。
說明:POJO 類屬性沒有初值是提醒使用者在需要使用時,必須自己顯式地進行賦值,任何 NPE 問題,或者入庫檢查,都由使用者來保證。
正例:數(shù)據(jù)庫的查詢結(jié)果可能是 null,因為自動拆箱,用基本數(shù)據(jù)類型接收有 NPE 風險。
反例:某業(yè)務的交易報表上顯示成交總額漲跌情況,即正負 x%,x 為基本數(shù)據(jù)類型,調(diào)用的 RPC 服務,調(diào)用不成功時,返回的是默認值,頁面顯示為 0%,這是不合理的,應該顯示成中劃線-。所以包裝數(shù)據(jù)類型的 null 值,能夠表示額外的信息,如:遠程調(diào)用失敗,異常退出。
\14.【強制】定義 DO
/DTO
/VO
等 POJO
類時,不要設(shè)定任何屬性默認值。
反例:POJO 類的 createTime 默認值為 new Date(),但是這個屬性在數(shù)據(jù)提取時并沒有置入具體值,在更新其它字段時又附帶更新了此字段,導致創(chuàng)建時間被修改成當前時間。
\15.【強制】序列化類新增屬性時,請不要修改 serialVersionUID
字段,避免反序列失??;如果完全不兼容升級,避免反序列化混亂,那么請修改 serialVersionUID
值。
說明:注意 serialVersionUID 不一致會拋出序列化運行時異常。
\16.【強制】構(gòu)造方法里面禁止加入任何業(yè)務邏輯,如果有初始化邏輯,請放在 init
方法中。
\17.【強制】POJO
類必須寫 toString
方法。使用 IDE
中的工具:source> generate toString時,如果繼承了另一個 POJO
類,注意在前面加一下super.toString
。
說明:在方法執(zhí)行拋出異常時,可以直接調(diào)用 POJO 的 toString()方法打印其屬性值,便于排查問題。
\18.【強制】禁止在 POJO
類中,同時存在對應屬性 xxx
的 isXxx()
和 getXxx()
方法。
說明:框架在調(diào)用屬性 xxx 的提取方法時,并不能確定哪個方法一定是被優(yōu)先調(diào)用到的。
\19.【推薦】使用索引訪問用 String
的 split
方法得到的數(shù)組時,需做最后一個分隔符后有無內(nèi)容的檢查,否則會有拋 IndexOutOfBoundsException 的風險。
說明:
String str = "a,b,c,,";
String[] ary = str.split(",");
// 預期大于 3,結(jié)果是 3
System.out.println(ary.length);
\20.【推薦】當一個類有多個構(gòu)造方法,或者多個同名方法,這些方法應該按順序放置在一起,便于閱讀,此條規(guī)則優(yōu)先于下一條。
\21.【推薦】 類內(nèi)方法定義的順序依次是:公有方法或保護方法 > 私有方法 > getter / setter方法。
說明:公有方法是類的調(diào)用者和維護者最關(guān)心的方法,首屏展示最好;保護方法雖然只是子類關(guān)心,也可能是“模板設(shè)計模式”下的核心方法;而私有方法外部一般不需要特別關(guān)心,是一個黑盒實現(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)體內(nèi),字符串的連接方式,使用 StringBuilder
的 append
方法進行擴展。
說明:下例中,反編譯出的字節(jié)碼文件顯示每次循環(huán)都會 new
出一個 StringBuilder
對象,然后進行 append
操作,最后通過 toString
方法返回 String
對象,造成內(nèi)存資源浪費。
反例:
String str = "start";
for (int i = 0; i < 100; i++) {
str = str + "hello";
}
\24.【推薦】final
可以聲明類、成員變量、方法、以及本地變量,下列情況使用 final
關(guān)鍵字:
? 1) 不允許被繼承的類,如:String
類。
? 2) 不允許修改引用的域?qū)ο?,如?code>POJO 類的域變量。
? 3) 不允許被覆寫的方法,如:POJO
類的 setter
方法。
? 4) 不允許運行過程中重新賦值的局部變量。
? 5) 避免上下文重復使用一個變量,使用 final
關(guān)鍵字可以強制重新定義一個變量,方便更好地進行重構(gòu)。
\25.【推薦】慎用 Object
的 clone
方法來拷貝對象。
說明:對象 clone 方法默認是淺拷貝,若想實現(xiàn)深拷貝,需覆寫 clone 方法實現(xiàn)域?qū)ο蟮纳疃缺闅v式拷貝。
\26.【推薦】類成員與方法訪問控制從嚴:
? 1) 如果不允許外部直接通過 new
來創(chuàng)建對象,那么構(gòu)造方法必須是 private
。
? 2) 工具類不允許有 public
或 default
構(gòu)造方法。
? 3) 類非 static
成員變量并且與子類共享,必須是 protected
。
? 4) 類非 static
成員變量并且僅在本類使用,必須是 private
。
? 5) 類 static
成員變量如果僅在本類使用,必須是 private
。
? 6) 若是 static
成員變量,考慮是否為 final
。
? 7) 類成員方法只供類內(nèi)部調(diào)用,必須是 private
。
? 8) 類成員方法只對繼承類公開,那么限制為 protected
。
說明:任何類、方法、參數(shù)、變量,嚴控訪問范圍。過于寬泛的訪問范圍,不利于模塊解耦。思考:如果是一個 private 的方法,想刪除就刪除,可是一個 public 的 service 成員方法或成員變量,刪除一下,不得手心冒點汗嗎?變量像自己的小孩,盡量在自己的視線內(nèi),變量作用域太大,無限制的到處跑,那么你會擔心的。
更多建議: