Java开发手册归纳知识点
Java開發手冊(泰山版)
開發手冊注意事項
1. POJO 類必須寫 toString 方法。使用 IDE 中的工具:source> generate toString
時,如果繼承了另一個 POJO 類,注意在前面加一下 super.toString。
?? ?說明:在方法執行拋出異常時,可以直接調用 POJO 的 toString()方法打印其屬性值,便于排查問題。
2. 禁止在 POJO 類中,同時存在對應屬性 xxx 的 isXxx()和 getXxx()方法。
說明:框架在調用屬性 xxx 的提取方法時,并不能確定哪個方法一定是被優先調用到,神坑之一。
3. 淺拷貝和深拷貝的區別,淺拷貝會把對象拷貝進來,修改一個屬性,原變量也會修改,是對地址的拷貝
? ?深拷貝會將原對象完整拷貝一份,將其復制一個對象
? ?
4. 關于日期的格式引用
?? ?日期格式化時,傳入 pattern 中表示年份統一使用小寫的 y。
?? ?說明:日期格式化時,yyyy 表示當天所在的年,而大寫的 YYYY 代表是 week in which year(JDK7 之后
?? ?引入的概念),意思是當天所在的周屬于的年份,一周從周日開始,周六結束,只要本周跨年,返回的 YYYY
?? ?就是下一年。
?? ?正例:表示日期和時間的格式如下所示:
?? ?new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
ps:24小時制度是用HH,12小時制度使用hh,大寫MM表示月,小寫mm表示分鐘
5. ?? ?獲取當前毫秒數:System.currentTimeMillis(); 而不是 new Date().getTime()。
說明:如果想獲取更加精確的納秒級時間值,使用 System.nanoTime 的方式。在 JDK8 中,針對統計時間
等場景,推薦使用 Instant 類。
6. 對于年份的天數需要進行動態寫入,不能定死為365天,可以使用
?? ?// 獲取今年的天數
?? ?int daysOfThisYear = LocalDate.now().lengthOfYear();
?? ?// 獲取指定某年的天數
?? ?LocalDate.of(2011, 1, 1).lengthOfYear()
7. 使用 Map 的方法 keySet()/values()/entrySet()返回集合對象時,不可以對其進行添
加元素操作,否則會拋出 UnsupportedOperationException 異常。
?? ?Collections 類返回的對象,如:emptyList()/singletonList()等都是 immutable list,
不可對其進行添加或者刪除元素的操作。
反例:如果查詢無結果,返回 Collections.emptyList()空集合對象,調用方一旦進行了添加元素的操作,就
會觸發 UnsupportedOperationException 異常。
?? ?在 subList 場景中,高度注意對父集合元素的增加或刪除,均會導致子列表的遍歷、
增加、刪除產生 ConcurrentModificationException 異常。
8. 使用集合轉數組的方法,必須使用集合的 toArray(T[] array),傳入的是類型完全一
致、長度為 0 的空數組。
反例:直接使用 toArray 無參方法存在問題,此方法返回值只能是 Object[]類,若強轉其它類型數組將出現
ClassCastException 錯誤。
正例:
List<String> list = new ArrayList<>(2);
list.add("guan");
list.add("bao");
String[] array = list.toArray(new String[0]);
?說明:使用 toArray 帶參方法,數組空間大小的 length,
1) 等于 0,動態創建與 size 相同的數組,性能最好。
2) 大于 0 但小于 size,重新創建大小等于 size 的數組,增加 GC 負擔。
3) 等于 size,在高并發情況下,數組創建完成之后,size 正在變大的情況下,負面影響與 2 相同。
4) 大于 size,空間浪費,且在 size 處插入 null 值,存在 NPE 隱患。
9. 使用 entrySet 遍歷 Map 類集合 KV,而不是 keySet 方式進行遍歷。
說明:keySet 其實是遍歷了 2 次,一次是轉為 Iterator 對象,另一次是從 hashMap 中取出 key 所對應的
value。而 entrySet 只是遍歷了一次就把 key 和 value 都放到了 entry 中,效率更高。如果是 JDK8,使用
Map.forEach 方法。
正例:values()返回的是 V 值集合,是一個 list 集合對象;keySet()返回的是 K 值集合,是一個 Set 集合對
象;entrySet()返回的是 K-V 值組合集合。
10. 高度注意 Map 類集合 K/V 能不能存儲 null 值的情況,如下表格:
集合類 ? ? ? ? ? ? ? ?Key ? ? ? ? ? ?Value ? ? ? ? ?Super ? ? ? ? 說明
Hashtable ? ? ? ? 不允許為 null ? 不允許為 null ? Dictionary ? ?線程安全
ConcurrentHashMap 不允許為 null ? 不允許為 null ? AbstractMap ?鎖分段技術(JDK8:CAS)
TreeMap ? ? ? ? ? 不允許為 null ? 允許為 null ? ?AbstractMap ? 線程不安全
HashMap ?? ??? ? ?允許為 null ? ? 允許為 null ? ?AbstractMap ? 線程不安全
反例:由于 HashMap 的干擾,很多人認為 ConcurrentHashMap 是可以置入 null 值,而事實上,存儲
null 值時會拋出 NPE 異常。
11. 合理利用好集合的有序性(sort)和穩定性(order),避免集合的無序性(unsort)和不穩
定性(unorder)帶來的負面影響。
說明:有序性是指遍歷的結果是按某種比較規則依次排列的。穩定性指集合每次遍歷的元素次序是一定的。
如:ArrayList 是 order/unsort;HashMap 是 unorder/unsort;TreeSet 是 order/sort。
12. Set集合去掉List集合中重復元素
public static void main(String[] args) {
?? ?
?? ?//利用set集合 去除ArrayList集合中的重復元素
?? ?ArrayList<String> list = new ArrayList<>();
?? ?list.add("1");
? ? list.add("1");
? ? list.add("2");
? ? list.add("2");
? ? list.add("3");
? ? list.add("3");
? ? list.add("4");
? ? list.add("4");
? ? System.out.println("去重前的List集合:"+list);
? ??
?? ?Set<String> set = new HashSet<>();
?? ?set.addAll(list);
?? ?System.out.println("Set集合:"+set);
?? ?
?? ?list.clear(); ? ? ? ? ? ?// 清空原有元素 放入被list去重后的元素
?? ?list.addAll(set);
?? ?System.out.println("去重后的List集合:"+list);
}
運行結果:
去重前的List集合:[1, 1, 2, 2, 3, 3, 4, 4]
Set集合:[1, 2, 3, 4]
去重后的List集合:[1, 2, 3, 4]
13. OOM(java.lang.OutOfMemoryError)內存用完,NPE(java.lang.NullPointerException)空指針
14. SimpleDateFormat 是線程不安全的類,一般不要定義為 static 變量,如果定義為 static,
必須加鎖,或者使用 DateUtils 工具類。
正例:注意線程安全,使用 DateUtils。亦推薦如下處理:
private static final ThreadLocal<DateFormat> df = new ThreadLocal<DateFormat>() {
?@Override
?protected DateFormat initialValue() {
?return new SimpleDateFormat("yyyy-MM-dd");
?}
};
說明:如果是 JDK8 的應用,可以使用 Instant(時間戳) 代替 Date,LocalDateTime 代替 Calendar,
DateTimeFormatter 代替 SimpleDateFormat,官方給出的解釋:simple beautiful strong immutable?
thread-safe。
15. 必須回收自定義的 ThreadLocal 變量,尤其在線程池場景下,線程經常會被復用,
如果不清理自定義的 ThreadLocal 變量,可能會影響后續業務邏輯和造成內存泄露等問題。
盡量在代理中使用 try-finally 塊進行回收。
正例:
objectThreadLocal.set(userInfo);
try {
?// ...
} finally {
?objectThreadLocal.remove();
}
16. 高并發時,同步調用應該去考量鎖的性能損耗。能用無鎖數據結構,就不要用鎖;能
鎖區塊,就不要鎖整個方法體;能用對象鎖,就不要用類鎖。
說明:盡可能使加鎖的代碼塊工作量盡可能的小,避免在鎖代碼塊中調用 RPC 方法。?
RPC的英文全程是Remote Procedure Call,也就是遠程過程調用,這里可以把過程理解為方法,也就是遠程方法調用。
在分布式系統中,因為每一個服務的邊界都很小,很有可能調用別的服務提供的方法,這就會出現服務A調用服務B中一個方法的需求,也就是遠程過程調用。
17. 悲觀鎖
總是假設最壞的情況,每次去拿數據的時候都認為別人會修改,
所以每次在拿數據的時候都會上鎖,這樣別人想拿這個數據就會阻塞直到它拿到鎖
(共享資源每次只給一個線程使用,其它線程阻塞,用完后再把資源轉讓給其它線程)。
傳統的關系型數據庫里邊就用到了很多這種鎖機制,比如行鎖,表鎖等,讀鎖,寫鎖等,
都是在做操作之前先上鎖。Java中synchronized和ReentrantLock等獨占鎖就是悲觀鎖思想的實現。
樂觀鎖
總是假設最好的情況,每次去拿數據的時候都認為別人不會修改,
所以不會上鎖,但是在更新的時候會判斷一下在此期間別人有沒有去更新這個數據,
可以使用版本號機制和CAS算法實現。樂觀鎖適用于多讀的應用類型,這樣可以提高吞吐量,
像數據庫提供的類似于write_condition機制,其實都是提供的樂觀鎖。
在Java中java.util.concurrent.atomic包下面的原子變量類
就是使用了樂觀鎖的一種實現方式CAS實現的。
兩種鎖的使用場景
從上面對兩種鎖的介紹,我們知道兩種鎖各有優缺點,不可認為一種好于另一種,
像樂觀鎖適用于寫比較少的情況下(多讀場景),即沖突真的很少發生的時候,
這樣可以省去了鎖的開銷,加大了系統的整個吞吐量。但如果是多寫的情況,
一般會經常產生沖突,這就會導致上層應用會不斷的進行retry,這樣反倒是降低了性能,
所以一般多寫的場景下用悲觀鎖就比較合適。
資金相關的金融敏感信息,使用悲觀鎖策略。
說明:樂觀鎖在獲得鎖的同時已經完成了更新操作,校驗邏輯容易出現漏洞,另外,樂觀鎖對沖突的解決策
略有較復雜的要求,處理不當容易造成系統壓力或數據異常,所以資金相關的金融敏感信息不建議使用樂觀
鎖更新。
正例:悲觀鎖遵循一鎖二判三更新四釋放的原則
18. HashMap 在容量不夠進行 resize 時由于高并發可能出現死鏈,導致 CPU 飆升,在
開發過程中注意規避此風險。
19. 在一個 switch 塊內,每個 case 要么通過 continue/break/return 等來終止,要么
注釋說明程序將繼續執行到哪一個 case 為止;在一個 switch 塊內,
都必須包含一個 default語句并且放在最后,即使它什么代碼也沒有。
說明:注意 break 是退出 switch 語句塊,而 return 是退出方法體。
20. 所有的類都必須添加創建者和創建日期。
說明:在設置模板時,注意 IDEA 的@author 為`${USER}`,而 eclipse 的@author 為`${user}`,大小寫有
區別,而日期的設置統一為 yyyy/MM/dd 的格式。
?正例:
?/**
* @author yangguanbao
* @date 2016/10/31
*/
21. 所有的枚舉類型字段必須要有注釋,說明每個數據項的用途
用法一:常量定義
public enum ColorEnum { ?
? RED, GREEN, BLANK, YELLOW ?
}
用法二:switch語句
enum ColorEnum {
? ? GREEN, YELLOW, RED
}
public class ColorTest {
? ? ColorEnum color = ColorEnum.RED;
?
? ? public void change() {
? ? ? ? switch (color) {
? ? ? ? ? ? case RED:
? ? ? ? ? ? ? ? color = ColorEnum.GREEN;
? ? ? ? ? ? ? ? break;
? ? ? ? ? ? case YELLOW:
? ? ? ? ? ? ? ? color = ColorEnum.RED;
? ? ? ? ? ? ? ? break;
? ? ? ? ? ? case GREEN:
? ? ? ? ? ? ? ? color = ColorEnum.YELLOW;
? ? ? ? ? ? ? ? break;
? ? ? ? }
? ? }
}
用法三:枚舉添加方法
public class EnumTest {
? ? public static void main(String[] args) {
? ? ? ? ErrorCodeEnum errorCode = ErrorCodeEnum.SUCCESS;
? ? ? ? System.out.println("狀態碼:" + errorCode.code() +?
? ? ? ? ? ? ? ? ? ? ? ? ? ?" 狀態信息:" + errorCode.msg());
? ? }
}
?
enum ErrorCodeEnum {
? ? SUCCESS(1000, "success"),
? ? PARAM_ERROR(1001, "parameter error"),
? ? SYS_ERROR(1003, "system error"),
? ? NAMESPACE_NOT_FOUND(2001, "namespace not found"),
? ? NODE_NOT_EXIST(3002, "node not exist"),
? ? NODE_ALREADY_EXIST(3003, "node already exist"),
? ? UNKNOWN_ERROR(9999, "unknown error");
?
? ? private int code;
? ? private String msg;
?
? ? ErrorCodeEnum(int code, String msg) {
? ? ? ? this.code = code;
? ? ? ? this.msg = msg;
? ? }
?
? ? public int code() {
? ? ? ? return code;
? ? }
?
? ? public String msg() {
? ? ? ? return msg;
? ? }
?
? ? public static ErrorCodeEnum getErrorCode(int code) {
? ? ? ? for (ErrorCodeEnum it : ErrorCodeEnum.values()) {
? ? ? ? ? ? if (it.code() == code) {
? ? ? ? ? ? ? ? return it;
? ? ? ? ? ? }
? ? ? ? }
? ? ? ? return UNKNOWN_ERROR;
? ? }
}
22. 在使用正則表達式時,利用好其預編譯功能,可以有效加快正則匹配速度。
說明:不要在方法體內定義:Pattern pattern = Pattern.compile(“規則”);
23. mybatis-plus中redis的使用
(1) 開啟mybatis-plus的二級緩存,application.yml文件進行配置,加入pom文件的依賴
(2) mapper類中加入緩存注解@CacheNamespace
(implementation= MybatisRedisCache.class,eviction= MybatisRedisCache.class)
(3) 編寫MybatisRedisCache實現Cache,定義讀寫鎖等
(4) 編寫RedisConfiguration 定義序列化規則和緩存方式和時間等自定義事項
(5) 編寫Util類,進行調用
24. 后臺輸送給頁面的變量必須加$!{var}——中間的感嘆號。
說明:如果 var 等于 null 或者不存在,那么${var}會直接顯示在頁面上。
25. 任何數據結構的構造或初始化,都應指定大小,避免數據結構無限增長吃光內存
26. 事務場景中,拋出異常被 catch 后,如果需要回滾,一定要注意手動回滾事務。
27. finally 塊必須對資源對象、流對象進行關閉,有異常也要做 try-catch。
?
28. 防止 NPE,是程序員的基本修養,注意 NPE 產生的場景:
?? ?1) 返回類型為基本數據類型,return 包裝數據類型的對象時,自動拆箱有可能產生 NPE。
?? ? 反例:public int f() { return Integer 對象}, 如果為 null,自動解箱拋 NPE。
?? ?2) 數據庫的查詢結果可能為 null。
?? ?3) 集合里的元素即使 isNotEmpty,取出的數據元素也可能為 null。
?? ?4) 遠程調用返回對象時,一律要求進行空指針判斷,防止 NPE。
?? ?5) 對于 Session 中獲取的數據,建議進行 NPE 檢查,避免空指針。
?? ?6) 級聯調用 obj.getA().getB().getC();一連串調用,易產生 NPE。
?? ?正例:使用 JDK8 的 Optional 類來防止 NPE 問題
?? ?
29. 在日志輸出時,字符串變量之間的拼接使用占位符的方式。
說明:因為 String 字符串的拼接會使用 StringBuilder 的 append()方式,有一定的性能損耗。使用占位符僅
是替換動作,可以有效提升性能。
正例:logger.debug("Processing trade with id: {} and symbol: {}", id, symbol);
30. 好的單元測試必須遵守 AIR 原則。
說明:單元測試在線上運行時,感覺像空氣(AIR)一樣并不存在,但在測試質量的保障上,卻是非常關鍵
的。好的單元測試宏觀上來說,具有自動化、獨立性、可重復執行的特點。
? A:Automatic(自動化)
? I:Independent(獨立性)
? R:Repeatable(可重復)
31. 單元測試應該是全自動執行的,并且非交互式的。測試用例通常是被定期執行的,執
行過程必須完全自動化才有意義。輸出結果需要人工檢查的測試不是一個好的單元測試。單元
測試中不準使用 System.out 來進行人肉驗證,必須使用 assert 來驗證。
32. 編寫單元測試代碼遵守 BCDE 原則,以保證被測試模塊的交付質量。
? B:Border,邊界值測試,包括循環邊界、特殊取值、特殊時間點、數據順序等。
? C:Correct,正確的輸入,并得到預期的結果。
? D:Design,與設計文檔相結合,來編寫單元測試。
? E:Error,強制錯誤信息輸入(如:非法數據、異常流程、業務允許外等),并得到預期的結果。
33. 用戶輸入的 SQL 參數嚴格使用參數綁定或者 METADATA 字段值限定,防止 SQL 注入,
禁止字符串拼接 SQL 訪問數據庫。
反例:某系統簽名大量被惡意修改,即是因為對于危險字符 # --沒有進行轉義,導致數據庫更新時,where
后邊的信息被注釋掉,對全庫進行更新。
34. 表單、AJAX 提交必須執行 CSRF 安全驗證。
說明:CSRF(Cross-site request forgery)跨站請求偽造是一類常見編程漏洞。對于存在 CSRF 漏洞的應用/
網站,攻擊者可以事先構造好 URL,只要受害者用戶一訪問,后臺便在用戶不知情的情況下對數據庫中用
戶參數進行相應修改。
CSRF(Cross-site Request Forgery)跨站請求偽造(或者縮寫為XSRF),
也被稱為"One Click Attack"或"Session Riding"(
曾被列為互聯網 20 大安全隱患之一),是一種借助社工對網站身份的惡意利用。
不大流行,但如果被成功利用,危害更大。
CSRF與XSS的區別:
XSS
通過盜取網站內的已有的用戶的身份,然后再執行相關操作
CSRF
通過偽裝(偽造、更改狀態的請求)用戶身份(即盜用身份),通過服務器身份認證后,然后發送惡意請求(服務器會認為請求是合法的),但是服務器給出響應肯定是給真實的那個用戶,
原理:
在瀏覽器中cookie在一段時間內是不會過期(不關閉或者退出瀏覽器),再次訪問都會默認登錄,這個應該都有體驗。如果在cookie存在期間,通過構造csrf腳本或包含csrf腳本的鏈接發送給用戶,得到信息后,再偽造成用戶身份,執行相關操作
基本流程:
用戶在某網站A進行登錄-------->身份驗證成功,返回cookie給用戶---------->攻擊者構建一個網站F,誘使用戶使用同一瀏覽器進入(前提:未退出網站A,一般都會有默認瀏覽器)------------->網站F收到用戶請求后,返回惡意代碼給用戶,強制他訪問網站A---------->用戶瀏覽器在網站A上執行相關操作(以已經持有的cookie)
使用過程
(1) 獲取用戶使用的修改信息的url
(2) 偽造url,將傳入的參數修改
(3) 將url誘騙用戶點擊
(4) 信息全部被更改
35. 表名、字段名必須使用小寫字母或數字,禁止出現數字開頭,禁止兩個下劃線中間只
出現數字。數據庫字段名的修改代價很大,因為無法進行預發布,所以字段名稱需要慎重考慮。
說明:MySQL 在 Windows 下不區分大小寫,但在 Linux 下默認是區分大小寫。因此,數據庫名、表名、
字段名,都不允許出現任何大寫字母,避免節外生枝。
正例:aliyun_admin,rdc_config,level3_name
反例:AliyunAdmin,rdcConfig,level_3_name
36. 主鍵索引名為 pk_字段名;唯一索引名為 uk_字段名;普通索引名則為 idx_字段名。
說明:pk_ 即 primary key;uk_ 即 unique key;idx_ 即 index 的簡稱。
37. 小數類型為 decimal,禁止使用 float 和 double。
說明:在存儲的時候,float 和 double 都存在精度損失的問題,很可能在比較值的時候,得到不正確的
結果。如果存儲的數據范圍超過 decimal 的范圍,建議將數據拆成整數和小數并分開存儲
38. 業務上具有唯一特性的字段,即使是組合字段,也必須建成唯一索引。
說明:不要以為唯一索引影響了 insert 速度,這個速度損耗可以忽略,但提高查找速度是明顯的;另外,
即使在應用層做了非常完善的校驗控制,只要沒有唯一索引,根據墨菲定律,必然有臟數據產生。
39. 不得使用外鍵與級聯,一切外鍵概念必須在應用層解決。
說明:(概念解釋)學生表中的 student_id 是主鍵,那么成績表中的 student_id 則為外鍵。如果更新學
生表中的 student_id,同時觸發成績表中的 student_id 更新,即為級聯更新。外鍵與級聯更新適用于單機
低并發,不適合分布式、高并發集群;級聯更新是強阻塞,存在數據庫更新風暴的風險;外鍵影響數據庫
的插入速度。
40. 分層領域模型規約:
? DO(Data Object):此對象與數據庫表結構一一對應,通過 DAO 層向上傳輸數據源對象。
? DTO(Data Transfer Object):數據傳輸對象,Service 或 Manager 向外傳輸的對象。
? BO(Business Object):業務對象,可以由 Service 層輸出的封裝業務邏輯的對象。
? Query:數據查詢對象,各層接收上層的查詢請求。注意超過 2 個參數的查詢封裝,禁止使用 Map 類
來傳輸。
? VO(View Object):顯示層對象,通常是 Web 向模板渲染引擎層傳輸的對象。
41. @EnableScheduling 開啟消息任務 ? ?@EnableAsync 開啟異步調用
總結
以上是生活随笔為你收集整理的Java开发手册归纳知识点的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 898 C. Phone Numbers
- 下一篇: 洛谷 P2596 [ZJOI2006]书