工作中Java集合的規(guī)范使用操作詳解
一、前言
現(xiàn)代軟件行業(yè)的高速發(fā)展對(duì)開發(fā)者的綜合素質(zhì)要求越來越高,因?yàn)椴粌H是編程知識(shí)點(diǎn),其它維度的知識(shí)點(diǎn)也會(huì)影響到軟件的最終交付質(zhì)量。比如:五花八門的錯(cuò)誤碼會(huì)人為地增加排查問題的難度;數(shù)據(jù)庫(kù)的表結(jié)構(gòu)和索引設(shè)計(jì)缺陷帶來的系統(tǒng)架構(gòu)缺陷或性能風(fēng)險(xiǎn);工程結(jié)構(gòu)混亂導(dǎo)致后續(xù)項(xiàng)目維護(hù)艱難;沒有鑒權(quán)的漏洞代碼容易被黑客攻擊等。依據(jù)約束力強(qiáng)弱及故障敏感性,規(guī)約依次分為【強(qiáng)制】、【推薦】、【參考】三大類。在延伸的信息中,“說明”對(duì)規(guī)約做了適當(dāng)擴(kuò)展和解釋;“正例”提倡什么樣的編碼和實(shí)現(xiàn)方式;“反例”說明需要提防的雷區(qū),以及真實(shí)的錯(cuò)誤案例。
現(xiàn)代軟件架構(gòu)的復(fù)雜性需要協(xié)同開發(fā)完成,如何高效地協(xié)同呢?無規(guī)矩不成方圓,無規(guī)范難以協(xié)同,比如,制定交通法規(guī)表面上是要限制行車權(quán),實(shí)際上是保障公眾的人身安全,試想如果沒有限速,沒有紅綠燈,誰還敢上路行駛?對(duì)軟件來說,適當(dāng)?shù)囊?guī)范和標(biāo)準(zhǔn)絕不是消滅代碼內(nèi)容的創(chuàng)造性、優(yōu)雅性,而是限制過度個(gè)性化,以一種普遍認(rèn)可的統(tǒng)一方式一起做事,提升協(xié)作效率,降低溝通成本。代碼的字里行間流淌的是軟件系統(tǒng)的血液,代碼質(zhì)量的提升是盡可能少踩坑,杜絕踩重復(fù)的坑,切實(shí)提升系統(tǒng)穩(wěn)定性,碼出質(zhì)量。
二、規(guī)范使用Java集合
1.【強(qiáng)制】關(guān)于 hashCode 和 equals 的處理,遵循如下規(guī)則:
1)只要覆寫 equals,就必須覆寫 hashCode。
2)因?yàn)?Set 存儲(chǔ)的是不重復(fù)的對(duì)象,依據(jù) hashCode 和 equals 進(jìn)行判斷,所以 Set 存儲(chǔ)的對(duì)象必須覆寫這兩種方法。
3)如果自定義對(duì)象作為 Map 的鍵,那么必須覆寫 hashCode 和 equals。
說明:String 因?yàn)楦矊懥?hashCode 和 equals 方法,所以可以愉快地將 String 對(duì)象作為 key 來使用。
2.【強(qiáng)制】判斷所有集合內(nèi)部的元素是否為空,使用 isEmpty() 方法,而不是 size() == 0 的方式。
說明:在某些集合中,前者的時(shí)間復(fù)雜度為 O(1),而且可讀性更好。
正例:
Map<String, Object> map = new HashMap<>(16);
if (map.isEmpty()) {
System.out.println("no element in this map.");
}3.【強(qiáng)制】在使用 java.util.stream.Collectors 類的 toMap() 方法轉(zhuǎn)為 Map 集合時(shí),一定要使用參數(shù)類型為 BinaryOperator,參數(shù)名為 mergeFunction 的方法,否則當(dāng)出現(xiàn)相同 key 時(shí)會(huì)拋出
IllegalStateException 異常。
說明:參數(shù) mergeFunction 的作用是當(dāng)出現(xiàn) key 重復(fù)時(shí),自定義對(duì) value 的處理策略。
正例:
List<Pair<String, Double>> pairArrayList = new ArrayList<>(3);
pairArrayList.add(new Pair<>("version", 12.10));
pairArrayList.add(new Pair<>("version", 12.19));
pairArrayList.add(new Pair<>("version", 6.28));
// 生成的 map 集合中只有一個(gè)鍵值對(duì):{version=6.28}
Map<String, Double> map = pairArrayList.stream()
.collect(Collectors.toMap(Pair::getKey, Pair::getValue, (v1, v2) -> v2));反例:
String[] departments = new String[]{"RDC", "RDC", "KKB"};
// 拋出 IllegalStateException 異常
Map<Integer, String> map = Arrays.stream(departments)
.collect(Collectors.toMap(String::hashCode, str -> str));4.【強(qiáng)制】在使用 java.util.stream.Collectors 類的 toMap() 方法轉(zhuǎn)為 Map 集合時(shí),一定要注意當(dāng) value為 null 時(shí)會(huì)拋 NPE 異常。
說明:在 java.util.HashMap 的 merge 方法里會(huì)進(jìn)行如下的判斷:
if (value == null || remappingFunction == null) throw new NullPointerException();
反例:
List<Pair<String, Double>> pairArrayList = new ArrayList<>(2);
pairArrayList.add(new Pair<>("version1", 8.3));
pairArrayList.add(new Pair<>("version2", null));
// 拋出 NullPointerException 異常
Map<String, Double> map = pairArrayList.stream()
.collect(Collectors.toMap(Pair::getKey, Pair::getValue, (v1, v2) -> v2));5.【強(qiáng)制】ArrayList 的 subList 結(jié)果不可強(qiáng)轉(zhuǎn)成 ArrayList,否則會(huì)拋出 ClassCastException 異常:java.util.RandomAccessSubList cannot be cast to java.util.ArrayList。
說明:subList() 返回的是 ArrayList 的內(nèi)部類 SubList,并不是 ArrayList 本身,而是 ArrayList 的一個(gè)視圖,對(duì)于SubList 的所有操作最終會(huì)反映到原列表上。同時(shí)注意數(shù)組越界異常。
6.【強(qiáng)制】使用 Map 的方法 keySet() / values() / entrySet() 返回集合對(duì)象時(shí),不可以對(duì)其進(jìn)行添加元素操作,否則會(huì)拋出 UnsupportedOperationException 異常。
7.【強(qiáng)制】Collections 類返回的對(duì)象,如:emptyList() / singletonList() 等都是 immutable list,不可對(duì)其進(jìn)行添加或者刪除元素的操作。
反例:如果查詢無結(jié)果,返回 Collections.emptyList() 空集合對(duì)象,調(diào)用方一旦在返回的集合中進(jìn)行了添加元素的操作,就會(huì)觸發(fā) UnsupportedOperationException 異常。
8.【強(qiáng)制】在 subList 場(chǎng)景中,高度注意對(duì)父集合元素的增加或刪除,均會(huì)導(dǎo)致子列表的遍歷、增加、刪除產(chǎn)生 ConcurrentModificationException 異常。
說明:抽查表明,90% 的程序員對(duì)此知識(shí)點(diǎn)都有錯(cuò)誤的認(rèn)知。
9.【強(qiáng)制】使用集合轉(zhuǎn)數(shù)組的方法,必須使用集合的 toArray(T[] array),傳入的是類型完全一致、長(zhǎng)度為0 的空數(shù)組。
反例:直接使用 toArray 無參方法存在問題,此方法返回值只能是 Object[]類,若強(qiáng)轉(zhuǎn)其它類型數(shù)組將出現(xiàn)ClassCastException 錯(cuò)誤。
正例:
List<String> list = new ArrayList<>(2);
list.add("guan");
list.add("bao");
String[] array = list.toArray(new String[0]);說明:使用 toArray 帶參方法,數(shù)組空間大小的 length:
1)等于 0,動(dòng)態(tài)創(chuàng)建與 size 相同的數(shù)組,性能最好。
2)大于 0 但小于 size,重新創(chuàng)建大小等于 size 的數(shù)組,增加 GC 負(fù)擔(dān)。
3)等于 size,在高并發(fā)情況下,數(shù)組創(chuàng)建完成之后,size 正在變大的情況下,負(fù)面影響與 2 相同。
4)大于 size,空間浪費(fèi),且在 size 處插入 null 值,存在 NPE 隱患。
10.【強(qiáng)制】使用 Collection 接口任何實(shí)現(xiàn)類的 addAll() 方法時(shí),要對(duì)輸入的集合參數(shù)進(jìn)行 NPE 判斷。
說明:在 ArrayList#addAll 方法的第一行代碼即 Object[] a = c.toArray();其中 c 為輸入集合參數(shù),如果為 null,則直接拋出異常。
11.【強(qiáng)制】使用工具類 Arrays.asList() 把數(shù)組轉(zhuǎn)換成集合時(shí),不能使用其修改集合相關(guān)的方法,它的 add/ remove / clear 方法會(huì)拋出 UnsupportedOperationException 異常。
說明:asList 的返回對(duì)象是一個(gè) Arrays 內(nèi)部類,并沒有實(shí)現(xiàn)集合的修改方法。Arrays.asList 體現(xiàn)的是適配器模式,只是轉(zhuǎn)換接口,后臺(tái)的數(shù)據(jù)仍是數(shù)組。
String[] str = new String[]{ "yang", "guan", "bao" };
List list = Arrays.asList(str);
第一種情況:list.add("yangguanbao"); 運(yùn)行時(shí)異常。
第二種情況:str[0] = "change"; list 中的元素也會(huì)隨之修改,反之亦然。
12.【強(qiáng)制】泛型通配符<? extends T>來接收返回的數(shù)據(jù),此寫法的泛型集合不能使用 add 方法,而<? super T>不能使用 get 方法,兩者在接口調(diào)用賦值的場(chǎng)景中容易出錯(cuò)。
說明:擴(kuò)展說一下 PECS(Producer Extends Consumer Super) 原則,即頻繁往外讀取內(nèi)容的,適合用<? extends T>,經(jīng)常往里插入的,適合用<? super T>
13.【強(qiáng)制】在無泛型限制定義的集合賦值給泛型限制的集合時(shí),在使用集合元素時(shí),需要進(jìn)行instanceof 判斷,避免拋出 ClassCastException 異常。
說明:畢竟泛型是在 JDK5 后才出現(xiàn),考慮到向前兼容,編譯器是允許非泛型集合與泛型集合互相賦值。
反例:
List<String> generics = null; List notGenerics = new ArrayList(10); notGenerics.add(new Object()); notGenerics.add(new Integer(1)); generics = notGenerics; // 此處拋出 ClassCastException 異常 String string = generics.get(0);
14.【強(qiáng)制】不要在 foreach 循環(huán)里進(jìn)行元素的 remove / add 操作。remove 元素請(qǐng)使用 iterator 方式,如果并發(fā)操作,需要對(duì) iterator 對(duì)象加鎖。
正例:
List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String item = iterator.next();
if (刪除元素的條件) {
iterator.remove();
}
}反例:
for (String item : list) {
if ("1".equals(item)) {
list.remove(item);
}
}說明:反例中的執(zhí)行結(jié)果肯定會(huì)出乎大家的意料,那么試一下把“1”換成“2”會(huì)是同樣的結(jié)果嗎?
15.【強(qiáng)制】在 JDK7 版本及以上,Comparator 實(shí)現(xiàn)類要滿足如下三個(gè)條件,不然 Arrays.sort,Collections.sort 會(huì)拋 IllegalArgumentException 異常。
說明:三個(gè)條件如下
1)x,y 的比較結(jié)果和 y,x 的比較結(jié)果相反。
2)x > y,y > z,則 x > z。
3)x = y,則 x,z 比較結(jié)果和 y,z 比較結(jié)果相同。
反例:下例中沒有處理相等的情況,交換兩個(gè)對(duì)象判斷結(jié)果并不互反,不符合第一個(gè)條件,在實(shí)際使用中可能會(huì)出現(xiàn)異常。
new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
return o1.getId() > o2.getId() ? 1 : -1;
}
};16.【推薦】泛型集合使用時(shí),在 JDK7 及以上,使用 diamond 語法或全省略。
說明:菱形泛型,即 diamond,直接使用<>來指代前邊已經(jīng)指定的類型。
正例:
// diamond 方式,即<> HashMap<String, String> userCache = new HashMap<>(16); // 全省略方式 ArrayList<User> users = new ArrayList(10);
17.【推薦】集合初始化時(shí),指定集合初始值大小。
說明:HashMap 使用構(gòu)造方法 HashMap(int initialCapacity) 進(jìn)行初始化時(shí),如果暫時(shí)無法確定集合大小,那么指定默認(rèn)值(16)即可。
正例:initialCapacity = (需要存儲(chǔ)的元素個(gè)數(shù) / 負(fù)載因子) + 1。注意負(fù)載因子(即 loaderfactor)默認(rèn)為 0.75,如果暫時(shí)無法確定初始值大小,請(qǐng)?jiān)O(shè)置為 16(即默認(rèn)值)。
反例:HashMap 需要放置 1024 個(gè)元素,由于沒有設(shè)置容量初始大小,隨著元素增加而不斷擴(kuò)容,resize() 方法總共會(huì)調(diào)用 8 次,反復(fù)重建哈希表和數(shù)據(jù)遷移。當(dāng)放置的集合元素個(gè)數(shù)達(dá)千萬級(jí)時(shí)會(huì)影響程序性能。
18.【推薦】使用 entrySet 遍歷 Map 類集合 KV,而不是 keySet 方式進(jìn)行遍歷。
說明:keySet 其實(shí)是遍歷了 2 次,一次是轉(zhuǎn)為 Iterator 對(duì)象,另一次是從 hashMap 中取出 key 所對(duì)應(yīng)的 value。而entrySet 只是遍歷了一次就把 key 和 value 都放到了 entry 中,效率更高。如果是 JDK8,使用 Map.forEach 方法。
正例:values() 返回的是 V 值集合,是一個(gè) list 集合對(duì)象;keySet() 返回的是 K 值集合,是一個(gè) Set 集合對(duì)象;entrySet() 返回的是 K-V 值組合的 Set 集合。
19、【推薦】高度注意 Map 類集合 K / V 能不能存儲(chǔ) null 值的情況,如下表格:

反例: 由于 HashMap 的干擾,很多人認(rèn)為 ConcurrentHashMap 是可以置入 null 值,而事實(shí)上,存儲(chǔ) null 值時(shí)會(huì)拋 出 NPE 異常。
20.【參考】合理利用好集合的有序性(sort)和穩(wěn)定性(order),避免集合的無序性(unsort)和不穩(wěn)定性(unorder)帶來的負(fù)面影響。
說明:有序性是指遍歷的結(jié)果是按某種比較規(guī)則依次排列的,穩(wěn)定性指集合每次遍歷的元素次序是一定的。如:ArrayList 是 order / unsort;HashMap 是 unorder / unsort;TreeSet 是 order / sort。
21.【參考】利用 Set 元素唯一的特性,可以快速對(duì)一個(gè)集合進(jìn)行去重操作,避免使用 List 的contains() 進(jìn)行遍歷去重或者判斷包含操作。
到此這篇關(guān)于工作中Java集合的規(guī)范使用操作詳解的文章就介紹到這了,更多相關(guān)Java集合內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot集成Elasticsearch過程實(shí)例
這篇文章主要介紹了SpringBoot集成Elasticsearch過程實(shí)例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-04-04
Mybatis的動(dòng)態(tài)Sql組合模式詳情
這篇文章主要介紹了Mybatis的動(dòng)態(tài)Sql組合模式詳情,這篇文章從組合模式的角度分析了Mybatis動(dòng)態(tài)sql的部分,SqlNode是組合模式的Component接口,更多相關(guān)內(nèi)容需要的小伙伴可以參考一下2022-08-08
Mybatis統(tǒng)計(jì)sql運(yùn)行時(shí)間的兩種方式
這篇文章主要介紹了Mybatis統(tǒng)計(jì)sql運(yùn)行時(shí)間的方案,Spring?Boot?+?Mybatis?web項(xiàng)目,統(tǒng)計(jì)sql運(yùn)行時(shí)間,用于分析慢sql,優(yōu)化系統(tǒng)速度,方案有兩種:自定義實(shí)現(xiàn)?Interceptor和使用現(xiàn)有依賴庫(kù)(Druid),文中通過代碼示例講解的非常詳細(xì),需要的朋友可以參考下2024-11-11
解決IDEA報(bào)錯(cuò)java無效的目標(biāo)發(fā)行版:22
在使用IDEA編譯項(xiàng)目時(shí),可能會(huì)遇到JDK版本不一致的錯(cuò)誤,這篇文章主要介紹了解決IDEA報(bào)錯(cuò)java無效的目標(biāo)發(fā)行版:22的相關(guān)資料,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-10-10
SpringBoot集成Mybatis的實(shí)現(xiàn)步驟
這篇文章主要介紹了SpringBoot集成Mybatis的實(shí)現(xiàn)步驟,本文通過SpringBoot +MyBatis 實(shí)現(xiàn)對(duì)數(shù)據(jù)庫(kù)學(xué)生表的查詢操作,需要的朋友可以參考下2020-12-12
SpringAMQP消息隊(duì)列(SpringBoot集成RabbitMQ方式)
這篇文章主要介紹了SpringAMQP消息隊(duì)列(SpringBoot集成RabbitMQ方式),具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-04-04
mybatis 批量將list數(shù)據(jù)插入到數(shù)據(jù)庫(kù)的實(shí)現(xiàn)
這篇文章主要介紹了mybatis 批量將list數(shù)據(jù)插入到數(shù)據(jù)庫(kù)的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-07-07
SpringBoot查詢數(shù)據(jù)庫(kù)導(dǎo)出報(bào)表文件方式
這篇文章主要介紹了SpringBoot查詢數(shù)據(jù)庫(kù)導(dǎo)出報(bào)表文件方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-04-04

