網(wǎng)站建設(shè)案例咨詢(xún)2023年5月最新疫情
一、前言
在 Java 開(kāi)發(fā)中,Arrays.asList() 是一個(gè)常用的工具方法,它允許開(kāi)發(fā)者快速將數(shù)組轉(zhuǎn)換為列表。盡管這個(gè)方法非常方便,但許多開(kāi)發(fā)者在使用時(shí)可能會(huì)遭遇一個(gè)常見(jiàn)的錯(cuò)誤:嘗試向由 Arrays.asList() 返回的列表中添加元素時(shí)拋出異常。本文將詳細(xì)探討這個(gè)現(xiàn)象的根本原因,并提供相關(guān)的使用示例和解決方案。。
二、事故回顧
這個(gè)問(wèn)題是出現(xiàn)開(kāi)發(fā)一個(gè)電商平臺(tái)的訂單系統(tǒng)時(shí),遇到了類(lèi)似的問(wèn)題。需要將一個(gè)訂單ID數(shù)組轉(zhuǎn)換為一個(gè)List,并在后續(xù)的業(yè)務(wù)邏輯中向這個(gè)List中添加新的訂單ID。
由于小伙伴經(jīng)驗(yàn)不足,使用了Arrays.asList()方法,部署到線(xiàn)上導(dǎo)致在添加新訂單ID時(shí)拋出了UnsupportedOperationException異常,整個(gè)訂單處理流程中斷,線(xiàn)上系統(tǒng)出現(xiàn)了嚴(yán)重的故障。
影響分析
這次事故對(duì)電商平臺(tái)造成了巨大的影響:
- 用戶(hù)體驗(yàn)下降: 由于訂單處理流程中斷,用戶(hù)無(wú)法正常下單,導(dǎo)致用戶(hù)體驗(yàn)大幅下降。
- 業(yè)務(wù)中斷: 訂單系統(tǒng)的故障直接影響到了整個(gè)電商平臺(tái)的業(yè)務(wù)運(yùn)營(yíng),造成了大量的訂單積壓。
- 經(jīng)濟(jì)損失: 由于業(yè)務(wù)中斷,平臺(tái)失去了大量的潛在收入,給公司帶來(lái)了不小的經(jīng)濟(jì)損失。
- 信任危機(jī): 頻繁的系統(tǒng)故障讓用戶(hù)對(duì)平臺(tái)的信任度下降,可能導(dǎo)致用戶(hù)流失。
后面也是及時(shí)修復(fù),吸取本次事故教訓(xùn)。
三、事故問(wèn)題描述
1、首先先簡(jiǎn)單描述一下這個(gè)事故問(wèn)題,廢話(huà)不多說(shuō),直接看實(shí)際業(yè)務(wù)代碼,也就將一個(gè)數(shù)組轉(zhuǎn)換為L(zhǎng)ist,并對(duì)這個(gè)List進(jìn)行增刪操作。
Integer[] arr = {1, 2};List<Integer> list = Arrays.asList(arr);list.add(3);
上述代碼看起來(lái),沒(méi)問(wèn)題,編譯器也沒(méi)有報(bào)錯(cuò)。但是運(yùn)行這段代碼時(shí),會(huì)拋出UnsupportedOperationException異常,提示我們不支持添加操作。這究竟是怎么回事呢?
所以這是不是個(gè)大坑,如果在實(shí)際開(kāi)發(fā)中沒(méi)有進(jìn)行自測(cè),必定不會(huì)釀成重大線(xiàn)上事故。
四、問(wèn)題分析
上述已經(jīng)知道這個(gè)使用Arrays.asList()將數(shù)組裝換成List會(huì)在add情況會(huì)報(bào)錯(cuò),接下來(lái)深入源碼進(jìn)行分析。
Arrays.asList()的內(nèi)部實(shí)現(xiàn)
Arrays.asList(arr)方法實(shí)際上返回的是一個(gè)Arrays類(lèi)的內(nèi)部類(lèi)ArrayList,而不是我們常用的java.util.ArrayList。這個(gè)內(nèi)部類(lèi)ArrayList繼承自AbstractList,并沒(méi)有實(shí)現(xiàn)add和remove方法。
Arrays類(lèi)的內(nèi)部類(lèi)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);}@Overridepublic int size() {return a.length;}@Overridepublic 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;}@Overridepublic E get(int index) {return a[index];}@Overridepublic E set(int index, E element) {E oldValue = a[index];a[index] = element;return oldValue;}@Overridepublic 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;}@Overridepublic boolean contains(Object o) {return indexOf(o) != -1;}@Overridepublic Spliterator<E> spliterator() {return Spliterators.spliterator(a, Spliterator.ORDERED);}@Overridepublic void forEach(Consumer<? super E> action) {Objects.requireNonNull(action);for (E e : a) {action.accept(e);}}@Overridepublic 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]);}}@Overridepublic void sort(Comparator<? super E> c) {Arrays.sort(a, c);}
}
可以看到并沒(méi)有實(shí)現(xiàn)add和remove方法,這兩個(gè)方法在父類(lèi)中,默認(rèn)拋出UnsupportedOperationException異常。
具體原因
下面是內(nèi)部源碼的分析
1.內(nèi)部類(lèi)ArrayList的限制:
Arrays.asList(arr)返回的內(nèi)部類(lèi)ArrayList是一個(gè)固定長(zhǎng)度的列表,它沒(méi)有實(shí)現(xiàn)add和remove方法。
當(dāng)我們調(diào)用list.add(3)時(shí),實(shí)際上調(diào)用的是AbstractList中的add方法,而這個(gè)方法直接拋出了UnsupportedOperationException異常。
2.源碼分析:
AbstractList的add方法如下:
五、解決方案
面對(duì)這一限制,開(kāi)發(fā)者可以采取以下幾種方式來(lái)處理 Arrays.asList() 返回的列表:
1、使用 new ArrayList<>() 創(chuàng)建可變列表
如果需要一個(gè)可變大小的列表,可以使用 new ArrayList<>(Arrays.asList(fruits)) 進(jìn)行轉(zhuǎn)換: ??這種方式創(chuàng)建了一個(gè)新的 ArrayList 實(shí)例,它的大小是可變的,因此可以自由添加、刪除元素。
List<String> dynamicList = new ArrayList<>(Arrays.asList("Apple", "Banana", "Cherry"));dynamicList.add("Date"); ?// 現(xiàn)在可以成功添加元素System.out.println(dynamicList);
2、直接初始化 ArrayList
另一種選擇是直接使用 ArrayList 的構(gòu)造函數(shù),并傳入數(shù)組元素: ??這種方法提供了更大的靈活性,適合需要?jiǎng)討B(tài)修改的場(chǎng)景。
List<String> dynamicList = new ArrayList<>();Collections.addAll(dynamicList, "Apple", "Banana", "Cherry");dynamicList.add("Date"); ?// 添加成功
3、使用流(Streams)
在 Java 8 及更高版本中,可以利用流的 API 來(lái)創(chuàng)建可變列表: ??這種方法簡(jiǎn)潔而現(xiàn)代化,適合喜歡函數(shù)式編程風(fēng)格的開(kāi)發(fā)者。
List<String> dynamicList = Stream.of("Apple", "Banana", "Cherry").collect(Collectors.toList());dynamicList.add("Date"); ?// 添加成功
轉(zhuǎn)載:小哈學(xué)Java