如東做網(wǎng)站公司百度廣告聯(lián)盟收益
大家好,我是靜幽水,一名大廠全棧程序員,今天給大家分享一個(gè)案例,看似簡(jiǎn)單。卻容易引發(fā)慘案。
事情是這樣的,最近組里來(lái)了一個(gè)實(shí)習(xí)生,因?yàn)轫?xiàng)目工作量大,人力比較緊張,所以就分配了一個(gè)簡(jiǎn)單的小需求給他,給一個(gè)接口增加一個(gè)出參,返回匹配到的規(guī)則編碼列表,規(guī)則編碼是數(shù)字類型,當(dāng)沒(méi)有匹配到規(guī)則時(shí),就返回默認(rèn)規(guī)則編碼。他的代碼是這樣寫(xiě)的:
int[] ruleIds = new int[roleList.size()];
for (int i = 0; i++; i<roleList.size() ) {ruleIds[i] = roleList.get(i).getId;
}
ok,這里看著還沒(méi)有問(wèn)題,但是在最后返回結(jié)果之前,他好像突然想起了,如果沒(méi)有匹配到規(guī)則的話,要返回一個(gè)默認(rèn)規(guī)則Id,于是他寫(xiě)下來(lái)這段代碼。
List<Integer> ruleIdList = Arrays.asList(ruleIds);
if (ruleIdList.size() == 0){ruleIdList.add(defaultId)
}
后來(lái),對(duì)于他的代碼,我們都覺(jué)得比較簡(jiǎn)單,代碼review的時(shí)候,都在review其他同時(shí)的,比較復(fù)雜的代碼邏輯,也就忽視了他的代碼,但往往你最輕視的地方,就是最容易出問(wèn)題的地方。就是這個(gè)ruleIdList.add(),導(dǎo)致在匹配不到規(guī)則的場(chǎng)景下,程序拋出UnsupportedOperationException異常。而他自測(cè)和UT也沒(méi)有覆蓋到這種場(chǎng)景,導(dǎo)致這個(gè)定時(shí)炸彈一直存在到上線之前。還好在上線前的前一天晚上,我們發(fā)現(xiàn)了這個(gè)問(wèn)題,才避免了慘案的發(fā)生。
后來(lái)我去詢問(wèn)他這樣寫(xiě)的動(dòng)機(jī)。為什么上面沒(méi)有用List而是用的數(shù)組,他給的解釋是因?yàn)橐舏nt類型,List里面只能放對(duì)象,我當(dāng)場(chǎng)吐血。難道不知道自動(dòng)裝箱拆箱嗎,那后面為啥再轉(zhuǎn)成list呢,他說(shuō),因?yàn)榍岸碎_(kāi)發(fā)要的list類型。好吧,既然事已發(fā)生,也就沒(méi)有過(guò)多去追問(wèn)。但是產(chǎn)生這個(gè)異常的原因,我還是想和大家分享一下。
Arrays.asList()方法是Java中將數(shù)組轉(zhuǎn)換為集合的常用方法。它接收一個(gè)數(shù)組作為參數(shù),并返回一個(gè)固定大小的List。這個(gè)List實(shí)際上是Arrays類的私有靜態(tài)內(nèi)部類ArrayList的實(shí)例。它實(shí)現(xiàn)了List接口,但是并沒(méi)有實(shí)現(xiàn)List接口中的一些修改集合結(jié)構(gòu)的方法,如add()、remove()等。
其實(shí)這并不是一種設(shè)計(jì)上的缺陷,而是特意為之,目的就是為了提高數(shù)組到集合的轉(zhuǎn)換效率,避免創(chuàng)建新的ArrayList對(duì)象。但是同時(shí)也帶來(lái)了一些限制,即不能對(duì)返回的List進(jìn)行修改操作。如果使用修改集合結(jié)構(gòu)的方法,例如add()、remove(),將會(huì)拋出UnsupportedOperationException異常,就像上面的代碼一樣。
為了更好地理解Arrays.asList()方法的局限性,我們來(lái)看一下它的源碼實(shí)現(xiàn)。Arrays.asList()方法是在Arrays類中定義的靜態(tài)方法。
// Arrays類的源碼
public static <T> List<T> asList(T... a) {return new ArrayList<>(a);
}
從源碼可以看出,Arrays.asList()方法接收可變參數(shù),而返回的是ArrayList類的實(shí)例。這個(gè)ArrayList類是Arrays類中的一個(gè)內(nèi)部類,實(shí)現(xiàn)了List接口。
// Arrays類的內(nèi)部類ArrayList的源碼
private static class ArrayList<E> extends AbstractList<E> implements RandomAccess, java.io.Serializable {private final E[] array;ArrayList(E[] array) {if (array == null) {throw new NullPointerException();}this.array = array;}public E get(int index) {return array[index];}public int size() {return array.length;}// 不支持修改集合結(jié)構(gòu)的方法public E set(int index, E element) {throw new UnsupportedOperationException();}public void add(int index, E element) {throw new UnsupportedOperationException();}public E remove(int index) {throw new UnsupportedOperationException();}
}
從ArrayList類的源碼可以看出,它繼承了AbstractList類并實(shí)現(xiàn)了RandomAccess接口,因此支持快速隨機(jī)訪問(wèn)。但是在ArrayList類中,修改集合結(jié)構(gòu)的方法都被重寫(xiě)為拋出UnsupportedOperationException異常,從而保證了對(duì)返回的List進(jìn)行修改的操作是不可行的。
除了Arrays.asList()方法,還存在其他類似的坑,它們?cè)谔幚砑蠒r(shí)也需要注意。
Collections.nCopies()
Collections.nCopies()方法用于創(chuàng)建一個(gè)指定元素重復(fù)多次的List。這個(gè)List同樣是固定大小的,不能進(jìn)行修改操作。
List<String> list = Collections.nCopies(3, "apple");
list.add("banana"); // 拋出UnsupportedOperationException異常
原理和Arrays.asList()類似,Collections.nCopies()方法返回的是一個(gè)AbstractList的實(shí)例,不支持修改集合結(jié)構(gòu)的操作。
Arrays.asList()的嵌套問(wèn)題
Arrays.asList()方法還存在一個(gè)嵌套的問(wèn)題。如果使用Arrays.asList()方法將一個(gè)二維數(shù)組轉(zhuǎn)換為L(zhǎng)ist,會(huì)得到一個(gè)List的嵌套結(jié)構(gòu),此時(shí)對(duì)內(nèi)層的List進(jìn)行修改同樣會(huì)拋出UnsupportedOperationException異常。
String[][] array2D = { { "apple", "banana" }, { "orange", "grape" } };
List<List<String>> nestedList = Arrays.asList(array2D);
nestedList.get(0).add("kiwi"); // 拋出UnsupportedOperationException異常
原因是這個(gè)嵌套的List中的元素仍然是固定大小的List。
這種設(shè)計(jì)是為了提高效率和節(jié)省內(nèi)存開(kāi)銷。在轉(zhuǎn)換數(shù)組到集合時(shí),能直接使用Arrays.asList()方法的時(shí)候,它是非常方便的。但是當(dāng)需要對(duì)集合進(jìn)行修改操作時(shí),應(yīng)該創(chuàng)建一個(gè)新的ArrayList對(duì)象,并使用addAll()方法將數(shù)組元素添加進(jìn)去,以避免UnsupportedOperationException異常的發(fā)生。
代碼示例:
String[] array = { "apple", "banana", "orange" };
List<String> list = new ArrayList<>();
Collections.addAll(list, array);
list.add("grape"); // 正常添加元素到集合中
或者如下:
List<String> Ids = new ArrayList<>(Arrays.asList(array));
Ids.add(id);
Collections.unmodifiableList()方法的不可修改特性
Collections.unmodifiableList()方法返回的是一個(gè)不可修改的List。它采用了裝飾器模式,對(duì)原始List進(jìn)行了封裝,重寫(xiě)了修改集合結(jié)構(gòu)的方法并拋出UnsupportedOperationException異常。
List<String> originalList = new ArrayList<>();
originalList.add("apple");
List<String> unmodifiableList = Collections.unmodifiableList(originalList);
unmodifiableList.add("banana"); // 拋出UnsupportedOperationException異常
總結(jié): 在使用Arrays.asList()方法將數(shù)組轉(zhuǎn)換為集合時(shí),注意其局限性。返回的List是一個(gè)固定大小的List,不支持修改集合結(jié)構(gòu)的方法。類似的坑還有Collections.nCopies()方法和Arrays.asList()的嵌套問(wèn)題,它們同樣需要注意不能對(duì)返回的集合進(jìn)行修改操作。