阿里巴巴Java開發手冊:編程規約.集合處理

1.【強制】關於hashCode和equals的處理,遵循如下規則:

1) 只要重寫equals,就必須重寫hashCode。

2) 因爲Set存儲的是不重複的對象,依據hashCode和equals進行判斷,所以Set存儲的對象必須重寫這兩個方法。

3) 如果自定義對象作爲Map的鍵,那麼必須重寫hashCode和equals。

說明:String重寫了hashCode和equals方法,所以我們可以非常愉快地使用String對象作爲key來使用。

當你把對象加入HashSet時,HashSet會先計算對象的hashcode值來判斷對象加入的位置,同時也會與該位置其他已經加入的對象的hashcode值作比較,如果沒有相符的hashcode,HashSet會假設對象沒有重複出現。但是如果發現有相同hashcode值的對象,會調用equals()方法來檢查hashcode相等的對象是否真的相同。如果兩者相同,HashSet就不會讓其加入操作成功。如果不同的話,就會重新散列到其他位置。這樣我們就大大減少了equals的次數,相應就大大提高了執行速度。

hashCode與equals:

如果兩個對象相等,則 hashcode 一定也是相同的

兩個對象有相同的hashcode值,它們不一定是相等的

兩個對象相等,對兩個對象分別調用equals方法都返回true

因此,equals方法被覆蓋過,則hashCode方法也必須被覆蓋

hashCode()的默認行爲是對堆上的對象產生獨特值。如果沒有重寫hashCode(),則該class的兩個對象無論如何都不會相等(即使這兩個對象指向相同的數據)

2.【強制】ArrayList的subList結果不可強轉成ArrayList,否則會拋出ClassCastException異常,即java.util.RandomAccessSubList cannot be cast to java.util.ArrayList。

說明:subList返回的是ArrayList的內部類SubList,並不是ArrayList而是ArrayList的一個視圖,對於SubList子列表的所有操作最終會反映到原列表上。該方法返回的是父list的一個視圖,從fromIndex(包含),到toIndex(不包含)。fromIndex=toIndex 表示子list爲空。

父子list做的非結構性修改(non-structural changes)都會影響到彼此:所謂的“非結構性修改”,是指不涉及到list的大小改變的修改。相反,結構性修改,指改變了list大小的修改。對於結構性修改,子list的所有操作都會反映到父list上。但父list的修改將會導致返回的子list失效。

3.【強制】在subList場景中,高度注意對原集合元素的增加或刪除,均會導致子列表的遍歷、增加、刪除產生ConcurrentModificationException 異常。

說明:父list的修改將會導致返回的子list失效,產生異常

4.【強制】使用集合轉數組的方法,必須使用集合的toArray(T[] array),傳入的是類型完全一樣的數組,大小就是list.size()。

正例:

List<String> list = new ArrayList<String>(2);
list.add("guan");
list.add("bao");
String[] array = new String[list.size()];
array = list.toArray(array);

反例:直接使用toArray無參方法存在問題,此方法返回值只能是Object[]類,若強轉其它類型數組將出現ClassCastException錯誤。

說明:使用toArray帶參方法,入參分配的數組空間不夠大時,toArray方法內部將重新分配內存空間,並返回新數組地址;如果數組元素個數大於實際所需,下標爲[ list.size()]的數組元素將被置爲null,其它數組元素保持原值,因此最好將方法入參數組大小定義與集合元素個數一致。

5.【強制】使用工具類Arrays.asList()把數組轉換成集合時,不能使用其修改集合相關的方法,它的add/remove/clear方法會拋出UnsupportedOperationException異常。

說明:asList的返回對象是一個Arrays內部類,並沒有實現集合的修改方法,包括add/remove/clear。Arrays.asList體現的是適配器模式,只是轉換接口,後臺的具體實現仍是數組。

String[] str = new String[] { "you", "wu" }; 
List list = Arrays.asList(str); 

第一種情況:list.add(“yangguanbao”);
運行時異常。

第二種情況:str[0]=“gujin”;那麼list.get(0)也會隨之修改。

6.【強制】泛型通配符<? extends T>來接收返回的數據,此寫法的泛型集合不能使用add方法,而<? super T>不能使用get方法,作爲接口調用賦值時易出錯。

說明:擴展說一下PECS(Producer Extends Consumer Super)原則:

第一、頻繁往外讀取內容的,適合用<? extends T>。


List<Apple> apples = new ArrayList<Apple>();
List<? extends Fruit> fruits = apples; //works, apple is a subclass of Fruit.
fruits.add(new Strawberry());        //compile error

fruits是一個Fruit的子類的List,由於Apple是Fruit的子類,因此將apples賦給fruits是合法的,但是編譯器會阻止將Strawberry加入fruits。因爲編譯器只知道fruits是Fruit的某個子類的List,但並不知道究竟是哪個子類,爲了類型安全,只好阻止向其中加入任何子類。那麼可不可以加入Fruit呢?很遺憾,也不可以。事實上,不能夠往一個使用了? extends的數據結構裏寫入任何的值。

但是,由於編譯器知道它總是Fruit的子類型,因此我們總可以從中讀取出Fruit對象:

第二、經常往裏插入的,適合用<? super T>。

Fruit fruit = fruits.get(0);
List<Fruit> fruits = new ArrayList<Fruit>();
List<? super Apple> = fruits;
fruits.add(new Apple());                 //work
fruits.add(new RedApple());              //work
fruits.add(new Fruit());                 //compile error 
fruits.add(new Object());                //compile error

這裏的fruits是一個Apple的超類(父類,superclass)的List。同樣地,出於對類型安全的考慮,我們可以加入Apple對象或者其任何子類(如RedApple)對象,但由於編譯器並不知道List的內容究竟是Apple的哪個超類,因此不允許加入特定的任何超類型。

而當我們讀取的時候,編譯器在不知道是什麼類型的情況下只能返回Object對象,因爲Object是任何Java類的最終祖先類。

具體可見:https://blog.51cto.com/flyingcat2013/1616068

7.【強制】不要在foreach循環裏進行元素的remove/add操作。remove元素請使用Iterator方式,如果併發操作,需要對Iterator對象加鎖。

正例:

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);
    }
}

說明:以上代碼的執行結果肯定會出乎大家的意料,那麼試一下把“1”換成“2”,會是同樣的結果嗎?

爲“1”時,能夠正常刪除,爲“2”時,報錯java.util.ConcurrentModificationException

具體原因可見:https://blog.csdn.net/yangbaggio/article/details/89920938

8. 【強制】 在JDK7版本及以上,Comparator實現類要滿足如下三個條件,不然Arrays.sort,Collections.sort會報IllegalArgumentException異常。

說明:三個條件如下

1) x,y的比較結果和y,x的比較結果相反。

2) x>y,y>z,則x>z。

3) x=y,則x,z比較結果和y,z比較結果相同。

這點我們需要格外注意,因爲在平常寫算法的過程中我們可能會用經常用到自定義比較器,在寫項目代碼時需要格外注意是否滿足這三個條件,以免發生錯誤。

反例:下例中沒有處理相等的情況,實際使用中可能會出現異常:

new Comparator<Student>() {
    @Override
    public int compare(Student o1, Student o2) {
        return o1.getId() > o2.getId() ? 1 : -1;
    }   
};

10. 【推薦】集合初始化時,指定集合初始值大小。

正例:initialCapacity = (需要存儲的元素個數 / 負載因子) + 1。注意負載因子(即loader factor)默認爲 0.75,如果暫時無法確定 初始值大小,請設置爲16(即默認值)。

反例: HashMap需要放置1024個元素,由於沒有設置容量初始大小,隨着元素不斷增加容量7次被迫擴大, resize需要重建hash表,嚴重影響性能。

說明:HashMap使用HashMap(int initialCapacity) 初始化。如果沒有指定初始大小,HashMap默認的初始化大小爲16。之後每次擴充,容量變爲原來的2倍,需要進行reszie,影響性能。

11.【推薦】使用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值組合集合。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章