切換語言為:簡體
利用 Arrays.asList() 陣列轉換成集合進行增刪操作報錯UnsupportedOperationException

利用 Arrays.asList() 陣列轉換成集合進行增刪操作報錯UnsupportedOperationException

  • 爱糖宝
  • 2024-09-06
  • 2071
  • 0
  • 0

引言

在Java開發中,陣列與集合之間的轉換是一個常見的操作。Arrays.asList()方法因其簡潔的語法和便捷的使用方式,被廣泛應用於將陣列轉換為集合(如List)。然而,這個看似簡單的方法背後卻隱藏著一個大坑,如果不小心處理,可能會導致線上事故,本文也是本人自身專案遇到的慘痛經歷,希望能夠透過講解這個問題,讓大家在實際工作中避開這個坑。

事故回顧

這個問題是出現開發一個電商平臺的訂單系統時,遇到了類似的問題。需要將一個訂單ID陣列轉換為一個List, 並在後續的業務邏輯中向這個List中新增新的訂單ID。由於個人經驗不足,使用了Arrays.asList()方法,部署到線上導致在新增新訂單ID時丟擲了UnsupportedOperationException異常, 整個訂單處理流程中斷,線上系統出現了嚴重的故障。

影響分析

這次事故對電商平臺造成了巨大的影響:

  1. 使用者體驗下降:由於訂單處理流程中斷,使用者無法正常下單,導致使用者體驗大幅下降。

  2. 業務中斷:訂單系統的故障直接影響到了整個電商平臺的業務運營,造成了大量的訂單積壓。

  3. 經濟損失:由於業務中斷,平臺失去了大量的潛在收入,給公司帶來了不小的經濟損失。

  4. 信任危機:頻繁的系統故障讓使用者對平臺的信任度下降,可能導致使用者流失。

後面也是及時修復,吸取本次事故教訓。

事故問題描述

首先先簡單描述一下這個事故問題,廢話不多說,直接看實際業務程式碼,也就將一個數組轉換為List,並對這個List進行增刪操作。

Integer[] arr = {1, 2};
List<Integer> list = Arrays.asList(arr);
list.add(3);


上述程式碼看起來,沒問題,編譯器也沒有報錯。但是執行這段程式碼時,會丟擲UnsupportedOperationException異常,提示我們不支援新增操作。這究竟是怎麼回事呢?

利用 Arrays.asList() 陣列轉換成集合進行增刪操作報錯UnsupportedOperationException

所以這是不是個大坑,如果在實際開發中沒有進行自測,必定不會釀成重大線上事故。

問題分析

上述已經知道這個使用Arrays.asList()將陣列裝換成List會在add情況會報錯,接下來深入原始碼進行分析。

Arrays.asList()的內部實現

Arrays.asList(arr)方法實際上返回的是一個Arrays類的內部類ArrayList,而不是我們常用的java.util.ArrayList。這個內部類ArrayList繼承自AbstractList,並沒有實現addremove方法。

利用 Arrays.asList() 陣列轉換成集合進行增刪操作報錯UnsupportedOperationException

Arrays類的內部類ArrayList完整原始碼如下:

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

    @Override
    public int size() {
        return a.length;
    }

    @Override
    public Object[] toArray() {
        return a.clone();
    }

    @Override
    @SuppressWarnings("unchecked")
    public <T> T[] toArray(T[] a) {
        int size = size();
        if (a.length < size)
            return Arrays.copyOf(this.a, size,
                                 (Class<? extends T[]>) a.getClass());
        System.arraycopy(this.a, 0, a, 0, size);
        if (a.length > size)
            a[size] = null;
        return a;
    }

    @Override
    public E get(int index) {
        return a[index];
    }

    @Override
    public E set(int index, E element) {
        E oldValue = a[index];
        a[index] = element;
        return oldValue;
    }

    @Override
    public int indexOf(Object o) {
        E[] a = this.a;
        if (o == null) {
            for (int i = 0; i < a.length; i++)
                if (a[i] == null)
                    return i;
        } else {
            for (int i = 0; i < a.length; i++)
                if (o.equals(a[i]))
                    return i;
        }
        return -1;
    }

    @Override
    public boolean contains(Object o) {
        return indexOf(o) != -1;
    }

    @Override
    public Spliterator<E> spliterator() {
        return Spliterators.spliterator(a, Spliterator.ORDERED);
    }

    @Override
    public void forEach(Consumer<? super E> action) {
        Objects.requireNonNull(action);
        for (E e : a) {
            action.accept(e);
        }
    }

    @Override
    public void replaceAll(UnaryOperator<E> operator) {
        Objects.requireNonNull(operator);
        E[] a = this.a;
        for (int i = 0; i < a.length; i++) {
            a[i] = operator.apply(a[i]);
        }
    }

    @Override
    public void sort(Comparator<? super E> c) {
        Arrays.sort(a, c);
    }
}


可以看到並沒有實現add和remove方法,這兩個方法在父類中,預設丟擲UnsupportedOperationException異常。

利用 Arrays.asList() 陣列轉換成集合進行增刪操作報錯UnsupportedOperationException

具體原因

下面是內部原始碼的分析

  1. 內部類ArrayList的限制

    • Arrays.asList(arr)返回的內部類ArrayList是一個固定長度的列表,它沒有實現addremove方法。

    • 當我們呼叫list.add(3)時,實際上呼叫的是AbstractList中的add方法,而這個方法直接丟擲了UnsupportedOperationException異常。

  2. 原始碼分析

    • AbstractListadd方法如下:

      public void add(int index, E element) {
          throw new UnsupportedOperationException();
      }


    • 這個方法沒有具體實現,而是直接丟擲異常。

對比java.util.ArrayList類,很明顯實現addremove方法,並沒有直接丟擲UnsupportedOperationException異常

利用 Arrays.asList() 陣列轉換成集合進行增刪操作報錯UnsupportedOperationException

解決方案

透過上述原始碼,爲了避免這個問題,需要使用java.util.ArrayListArrays.asList(arr)返回的列表進行封裝,而不是直接使用,這樣就可以正常進行增刪操作了,詳細步驟如下;

詳細步驟

在上述程式碼的基礎上進行程式碼最佳化修改。

  1. 建立陣列

    Integer[] arr = {1, 2};


  2. 轉換為List

    List<Integer> list = Arrays.asList(arr);


  3. 封裝為可變List:這個就是問題解決的關鍵,使用java.util.ArrayListArrays.asList(arr)進行封裝

    ArrayList<Integer> arrayList = new ArrayList<>(Arrays.asList(arr));


  4. 進行增刪操作

    arrayList.add(3); // 正常新增元素 
    arrayList.remove(1); // 正常刪除元素


程式碼示例

最終完整程式碼如下:

public class Arrays_BugDemo {
    public static void main(String[] args) {
        Integer[] arr = {1, 2};
        List<Integer> list = Arrays.asList(arr);
        // 使用ArrayList進行封裝
        ArrayList<Integer> arrayList = new ArrayList<>(Arrays.asList(arr));
        try {
            list.add(3); // 這裏會報錯
        } catch (UnsupportedOperationException e) {
            System.out.println("list.add(3) 報錯: " + e.getMessage());
        }
        arrayList.add(3); // 現在可以正常新增元素
        arrayList.forEach(System.out::println);
    }
}


執行結果,程式可以正常執行,並且最終arrayList可以新增新元素。

利用 Arrays.asList() 陣列轉換成集合進行增刪操作報錯UnsupportedOperationException

總結

透過本文的詳細講解,我們可以得出以下幾點結論:

  1. Arrays.asList(arr)返回的是一個固定長度的List集合,沒有addremove具體實現方法,不能進行增刪操作,否則會報錯UnsupportedException

  2. 如果需要進行增刪操作,可以使用java.util.ArrayList進行封裝,如:new ArrayList<>(Arrays.asList(arr))

希望本文能夠幫助大家避免類似的線上事故,提高程式碼的健壯性和可靠性。在實際開發中,務必注意Arrays.asList()的使用場景,確保程式碼的正確性和穩定性。同時,也應該從這次事故中吸取教訓,加強程式碼審查和測試,避免類似問題的再次發生。

0則評論

您的電子郵件等資訊不會被公開,以下所有項目均必填

OK! You can skip this field.