Java開發人員最容易出現的幾類錯誤
一、把陣列轉成ArrayList
List<String> list = Arrays.asList(arr);
//以下帶虛擬碼來自Arrays類中
public static <T> List<T> asList(T... a) {
return new ArrayList<>(a);
}
private static class ArrayList<E> extends AbstractList<E>
implements RandomAccess, java.io.Serializable
{
private static final long serialVersionUID = -2764017481108945198L;
private final E[] a;
ArrayList(E[] array) {
a = Objects.requireNonNull(array);
}
}
使用Arrays.asList()
方法可以得到一個ArrayList,但是得到這個ArrayList
其實是定義在Arrays類中的一個私有的靜態內部類。這個類雖然和java.util.ArrayList
同名,但是並不是同一個類。java.util.Arrays.ArrayList
類中實現了set()
,get()
,contains()
等方法,但是並沒有定義向其中增加元素的方法。也就是說通過Arrays.asList()
得到的ArrayList的大小是固定的。
二、在迴圈中刪除列表中的元素
ArrayList<String> list = new ArrayList<String>(Arrays.asList("a","b","c","d"));for(int i=0;i<list.size();i++){ list.remove(i); } System.out.println(list);
輸出結果:
[b,d]
以上程式碼的目的是想遍歷刪除list中所有元素,但是結果卻沒有成功。原因是忽略了一個關鍵的問題:當一個元素被刪除時,列表的大小縮小並且下標也會隨之變化,所以當你想要在一個迴圈中用下標刪除多個元素的時候,它並不會正常的生效。
也有些人知道以上程式碼的問題就由於陣列下標變換引起的。所以,他們想到使用增強for迴圈的形式:
ArrayList<String> list = new ArrayList<String>(Arrays.asList("a","b","c","d"));for(String s:list){ if(s.equals("a")){ list.remove(s); } }
但是,很不幸的是,以上程式碼會丟擲ConcurrentModificationException
,有趣的是,如果在remove操作後增加一個break,程式碼就不會報錯:
ArrayList<String> list = new ArrayList<String>(Arrays.asList("a","b","c","d")); for(String s:list){ if(s.equals("a")){ list.remove(s); break; } }
迭代器被建立之後會建立一個指向原來物件的單鏈索引表,當原來的物件數量發生變化時,這個索引表的內容不會同步改變,所以當索引指標往後移動的時候就找不到要迭代的物件,所以按照 fail-fast 原則 迭代器會馬上丟擲java.util.ConcurrentModificationException
異常。
所以,正確的在遍歷過程中刪除元素的方法應該是使用Iterator:
ArrayList<String> list = new ArrayList<String>(Arrays.asList("a", "b", "c", "d")); Iterator<String> iter = list.iterator(); while (iter.hasNext()) { String s = iter.next(); if (s.equals("a")) { iter.remove(); } }
next()
方法必須在呼叫remove()
方法之前呼叫。如果在迴圈過程中先呼叫remove()
,再呼叫next()
,就會導致異常ConcurrentModificationException
。原因如上。
三、迷之求和
public void test_add(){ int num = 0; for (int i = 0; i < 100; i++) { num = num++; } System.out.println(num); }
最終num
結果為 0,num++
根本沒起啥作用。因為後++,是先用結果,在++操作,不會給賦值。正確寫法是:num = ++ num;
四、無用日誌
public boolean ruleEngine(MatterReq req) { try { // 業務流程 } catch (Exception e) { logger.error(e); // 只打異常,不打入參資訊 } }
五、耗時遍歷
public void test_LinkedList() { // 初始化100萬資料 List<Integer> list = new LinkedList<Integer>(1000000); // 遍歷求和 int sum = 0; for (int i = 0; i < list.size(); i++) { sum += list.get(i); } }
乍一看可能覺得沒什麼問題,但是這個遍歷求和會非常慢。主要因為連結串列的資料結構,每一次list.get(i)
都是從連結串列的頭開始查詢,與ArrayList
不同,LinkedList
它時間複雜度是O(n)。那如果說你不知道對方傳過來的是LinkedList
還是ArrayList
呢,其實可以通過list instanceof RandomAccess
進行判斷。ArrayList
有隨機訪問的實現,LinkedList
是沒有。同時也可以使用增強的for迴圈或者Iterator
進行遍歷。