首先讓我們來看一段代碼:
String[] strings = new String[]{"hello","world"};List<String> stringList = Arrays.asList(strings);stringList.add("java");咋眼一看這段代碼沒什么問題,然而這段帶卻拋出了一個名為:UnsupportedOperationException的異常。看到異常時有些懵逼,查了資料說只是因為Arrays自身實現(xiàn)的問題。于是乎,我查看了源碼。
Arrays.toList(T… t) 方法返回的是Arrays的一個內(nèi)部類ArrayList,大家可不要被這個名字騙了,此ArrayList非彼ArrayList啊,這完全就是李鬼啊。
先看一下這個ArrayList的聲明方式吧:
PRivate static class ArrayList<E> extends AbstractList<E> implements Randomaccess, java.io.Serializable我們經(jīng)常使用的ArrayList的聲明方式為:
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable再看看AbstractList的聲明方式為:
public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E>現(xiàn)在可以看出我們經(jīng)常使用的ArrayList相比Arrays內(nèi)部的ArrayList來說多實現(xiàn)了一個Cloneable接口,至于為什么不給Arrays內(nèi)部的ArrayList實現(xiàn)Cloneable接口,這與當初設(shè)計這個內(nèi)部類的目的是有關(guān)系的,后面將會說到這個內(nèi)部類的作用。
先看看Arrays.ArrayList的元素存儲區(qū)的聲明:
private final E[] a;而java.util.ArrayList的存儲區(qū)是這樣聲明的:
transient Object[] elementData;這么一對比,李鬼假的簡直太粗暴了,連一個優(yōu)雅的名字都不舍得想。從聲明上可以看出Arrays.ArrayList中的元素存儲數(shù)組是一個不可變的數(shù)組引用,由于數(shù)組的長度本身是不可變的,所以Arrays.ArrayList從元素存儲上就不支持長度變化,那么肯定是不允許add和remove操作了,所以在該類中就未對add和remove進行實現(xiàn),直接調(diào)用了父類AbstractList中聲明的方法:
public void add(int index, E element) { throw new UnsupportedOperationException();}public E remove(int index) { throw new UnsupportedOperationException();}public E set(int index, E element) { throw new UnsupportedOperationException();}但凡是需要使用到這三個方法的操作均會直接拋出UnsupportedOperationException異常。
java.util.ArrayList之所以可以變長,是因為使用到了Arrays.copyOf(T[] original, int newLength)方法,具體實現(xiàn):
/** * Increases the capacity to ensure that it can hold at least the * number of elements specified by the minimum capacity argument. * * @param minCapacity the desired minimum capacity */private void grow(int minCapacity) { // overflow-conscious code int oldCapacity = elementData.length; int newCapacity = oldCapacity + (oldCapacity >> 1); if (newCapacity - minCapacity < 0) newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); // minCapacity is usually close to size, so this is a win: elementData = Arrays.copyOf(elementData, newCapacity);}而且此類內(nèi)部是實現(xiàn)了add、remove和set三個方法:
/** * Inserts the specified element at the specified position in this * list. Shifts the element currently at that position (if any) and * any subsequent elements to the right (adds one to their indices). * * @param index index at which the specified element is to be inserted * @param element element to be inserted * @throws IndexOutOfBoundsException {@inheritDoc} */public void add(int index, E element) { rangeCheckForAdd(index); ensureCapacityInternal(size + 1); // Increments modCount!! System.arraycopy(elementData, index, elementData, index + 1, size - index); elementData[index] = element; size++;}/** * Removes the element at the specified position in this list. * Shifts any subsequent elements to the left (subtracts one from their * indices). * * @param index the index of the element to be removed * @return the element that was removed from the list * @throws IndexOutOfBoundsException {@inheritDoc} */public E remove(int index) { rangeCheck(index); modCount++; E oldValue = elementData(index); int numMoved = size - index - 1; if (numMoved > 0) System.arraycopy(elementData, index+1, elementData, index, numMoved); elementData[--size] = null; // clear to let GC do its work return oldValue;}/** * Removes the first occurrence of the specified element from this list, * if it is present. If the list does not contain the element, it is * unchanged. More formally, removes the element with the lowest index * <tt>i</tt> such that * <tt>(o==null ? get(i)==null : o.equals(get(i)))</tt> * (if such an element exists). Returns <tt>true</tt> if this list * contained the specified element (or equivalently, if this list * changed as a result of the call). * * @param o element to be removed from this list, if present * @return <tt>true</tt> if this list contained the specified element */public boolean remove(Object o) { if (o == null) { for (int index = 0; index < size; index++) if (elementData[index] == null) { fastRemove(index); return true; } } else { for (int index = 0; index < size; index++) if (o.equals(elementData[index])) { fastRemove(index); return true; } } return false;}在查看源碼的過程中也許有同學(xué)已經(jīng)注意到了java.util.ArrayList中使用了Arrays.copy和System.arraycopy,這兩者之間的區(qū)別以后有機會再深入探討。
到此我們也應(yīng)該知道為什么Arrays.toList返回的List不支持add方法了,其根本原因是因為存儲元素的數(shù)組未不可變數(shù)組。
縱觀jdk中所有開放的List實現(xiàn)類中沒有一個提供以數(shù)組為參數(shù)的構(gòu)造方法。從數(shù)據(jù)結(jié)構(gòu)來看,數(shù)組是一種特殊的鏈表(不可變長),正是因為這個特殊性,造成了在java中的數(shù)組無法使用鏈表(List)的一些方法,使用上與聊表對象的使用方式不同,僅支持設(shè)值、遍歷與排序。再多的功能,如元素的新增、刪除以及子連的獲取功能均無法直接調(diào)用系統(tǒng)接口,只能自行編寫代碼。本來都是鏈表,卻因為其中的一些特殊性,變成了兩種截然不同的對象,為了打通數(shù)組與List之間的堡壘,Arrays.ArrayList誕生了,讓我們可以使用更短的代碼實現(xiàn)數(shù)組與List之間的轉(zhuǎn)換。
String[] strings = new String[]{"hello","world"};List<String> stringList = new ArrayList(Arrays.asList(strings));stringList.add("java");Arrays.ArrayList**僅可用于作為構(gòu)造List的參數(shù)**,其他時候均可認為其就是一個數(shù)組。
那么我們上面看到了Arrays.ArrayList未實現(xiàn)Cloneable接口,是因為Arrays.ArrayLists的狀態(tài)本身就不可變,當然其中元素可變,實現(xiàn)之后并無任何意義。
這一個異常的拋出,讓我深入jdk源碼內(nèi)部了解了問題的根源,更讓我接觸到了大牛們寫代碼的思想:明確類的設(shè)計目的,從而明確類的功能邊界,在編碼的過程中絕不越界完成,也不重復(fù)完成功能。
新聞熱點
疑難解答