1. 程式人生 > 實用技巧 >Java開發人員最容易出現的幾類錯誤

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進行遍歷。