一、前言
本文參考《阿里巴巴Java開發手冊》,這本書主要定義了一些代碼的規范以及一些注意事項。我只根據我自己的不足,摘錄了一些內容,方便以后查閱。
二、讀書筆記
- 命名
1、代碼中的命名均不能以下划線或美元符號開始,也不能以下划線或美元符號結束。
-
常量定義
-
OOP規約
-
集合處理
1、ArrayList的subList結果不可強轉成ArrayList,否則會拋出 ClassCastException異常: java.util.RandomAccessSubList cannot be cast to java.util.ArrayList。subList 返回的是 ArrayList 的內部類 SubList,並不是 ArrayList ,而是ArrayList 的一個視圖,對於 SubList 子列表的所有操作最終會反映到原列表上。
2、在 subList 場景中, 高度注意對原集合元素個數的修改,會導致子列表的遍歷、增加、刪除均產生 ConcurrentModificationException 異常。
3、使用集合轉數組的方法,必須使用集合的 toArray(T[] array),傳入的是類型完全一樣的數組,大小就是 list.size()。
4、使用工具類 Arrays.asList()把數組轉換成集合時,不能使用其修改集合相關的方法,它的 add/remove/clear 方法會拋出 UnsupportedOperationException 異常。
5、泛型通配符<? extends T>來接收返回的數據,此寫法的泛型集合不能使用 add 方法, 而<? super T>不能使用 get 方法,做為接口調用賦值時易出錯。
6、不要在 foreach 循環里進行元素的 remove/add 操作。 remove 元素請使用 Iterator方式,如果並發操作,需要對 Iterator 對象加鎖。
正例: Iterator<String> it = a.iterator(); while (it.hasNext()) { String temp = it.next(); if (刪除元素的條件) { it.remove(); } } 反例: List<String> a = new ArrayList<String>(); a.add("1"); a.add("2"); for (String temp : a) { if ("1".equals(temp)) { a.remove(temp); } }
7、集合初始化時, 指定集合初始值大小。
9、使用 entrySet 遍歷 Map 類集合 KV,而不是 keySet 方式進行遍歷。
10、在 java 集合類庫中,List 的 contains 方法普遍時間復雜度是 O(n) ,如果在代碼中需要頻繁調用 contains 方法查找數據,可以先將 list 轉換成 HashSet 實現,將 O(n) 的時間復雜度降為 O(1) 。
11、高度注意 Map 類集合 K/V 能不能存儲 null 值的情況,如下表格:
Hashtable | 不允許為 null | 不允許為 null | Dictionary | 線程安全 |
ConcurrentHashMap | 不允許為 null | 不允許為 null | AbstractMap | 分段鎖技術 |
TreeMap | 不允許為 null | 允許為 null | AbstractMap | 線程不安全 |
HashMap | 允許為 null | 允許為 null | AbstractMap | 線程不安全 |
-
控制語句
1、在 if/else/for/while/do 語句中必須使用大括號。 即使只有一行代碼,避免使用單行的形式: if (condition) statements;
2、表達異常的分支時, 少用 if-else 方式。如果非得使用 if()...else if()...else...方式表達邏輯,避免后續代碼維護困難, 請勿超過 3 層。
3、除常用方法(如 getXxx/isXxx)等外,不要在條件判斷中執行其它復雜的語句,將復雜邏輯判斷的結果賦值給一個有意義的布爾變量名,以提高可讀性。
正例: final boolean existed = (file.open(fileName, "w") != null) && (...) || (...); if (existed) { ... } 反例: if ((file.open(fileName, "w") != null) && (...) || (...)) { ... }
-
異常處理
1、Java 類庫中定義的一類 RuntimeException 可以通過預先檢查進行規避,而不應該通過 catch 來處理,比如: IndexOutOfBoundsException, NullPointerException 等等。
- 日志規約
1、應用中不可直接使用日志系統(Log4j、 Logback) 中的 API,而應依賴使用日志框架SLF4J 中的 API,使用門面模式的日志框架,有利於維護和各個類的日志處理方式統一。
private static final Logger logger = LoggerFactory.getLogger(Abc.class);
2、日志文件推薦至少保存 15 天,因為有些異常具備以“周”為頻次發生的特點。
3、對 trace/debug/info 級別的日志輸出,必須使用條件輸出形式或者使用占位符的方式。
說明: logger.debug("Processing trade with id: " + id + " symbol: " + symbol); 如果日志級別是 warn,上述日志不會打印,但是會執行字符串拼接操作,如果 symbol 是對象,會執行 toString()方法,浪費了系統資源,執行了上述操作,最終日志卻沒有打印。 正例: (條件) if (logger.isDebugEnabled()) { logger.debug("Processing trade with id: " + id + " symbol: " + symbol); } 正例: (占位符) logger.debug("Processing trade with id: {} symbol : {} ", id, symbol);
4、避免重復打印日志,浪費磁盤空間,務必在 log4j.xml 中設置 additivity=false。
<logger name="com.taobao.dubbo.config" additivity="false">
5、謹慎地記錄日志。生產環境禁止輸出 debug 日志; 有選擇地輸出 info 日志; 如果使用 warn 來記錄剛上線時的業務行為信息,一定要注意日志輸出量的問題,避免把服務器磁盤撐爆,並記得及時刪除這些觀察日志 。
- 其他
1、在使用正則表達式時,利用好其預編譯功能,可以有效加快正則匹配速度。
2、后台輸送給頁面的變量必須加$!{var}——中間的感嘆號。
3、不要在視圖模板中加入任何復雜的邏輯。
4、任何數據結構的構造或初始化,都應指定大小,避免數據結構無限增長吃光內存。
5、對於“明確停止使用的代碼和配置”,如方法、變量、類、配置文件、動態配置屬性等要堅決從程序中清理出去,避免造成過多垃圾。
6、new BigDecimal(double) 存在精度損失風險,在精確計算或值比較的場景中可能會導致業務邏輯異常。請使用 BigDecimal.valueOf(double) 。
7、SimpleDateFormat 是線程不安全的類,一般不要定義為 static 變量,如果定義為static,必須加鎖,或者使用 DateUtils 工具類。
正例: 注意線程安全,使用DateTools 。亦推薦如下處理:
public class DateTools
{
private static ThreadLocal<SimpleDateFormat> t1=new ThreadLocal<>();
public static SimpleDateFormat getSdf(String pattern){
SimpleDateFormat simpleDateFormat = t1.get();
if (simpleDateFormat==null){
simpleDateFormat=new SimpleDateFormat(pattern);
}
t1.set(simpleDateFormat);
return simpleDateFormat;
}
}
說明: 如果是 JDK8 的應用,可以使用 Instant 代替 Date, LocalDateTime 代替 Calendar,DateTimeFormatter代替Simpledateformatter,官方給出的解釋:simple beautiful strong immutable thread-safe。

// Instant 相當於 java.util.Date 類 Instant instant = Instant.now(); instant.getLong(INSTANT_SECONDS); //時間戳 // LocalDate 不包含時間的日期 LocalDate localDate = LocalDate.now(); // 自定義日期時間 LocalDate of = LocalDate.of(2019, 10, 1); LocalDate parse = LocalDate.parse("2019-09-02"); // 日期加減 LocalDate days = parse.plusMonths(1L).minusDays(1L); // 日期比較 System.out.println(of.isBefore(days)); System.out.println(of.isAfter(days)); System.out.println(of.equals(days)); // LocalTime 不包含日期的時間 LocalTime localTime = LocalTime.now(); System.out.println(LocalTime.MAX); System.out.println(LocalTime.MIN); // LocalDateTime 包含了日期及時間,沒有偏移信息(時區),相當於 java.util.Calendar 類 LocalDateTime localDateTime = LocalDateTime.now(); LocalDateTime atTime = localDate.atTime(LocalTime.MAX); LocalDateTime atDate = localTime.atDate(LocalDate.of(2019, 10, 14)); // 時間日期格式化,相當於 java.text.SimpleDateFormat LocalDateTime dateTime = LocalDateTime.now(); dateTime.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME); dateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
書籍鏈接: 阿里巴巴Java開發手冊