最近在網上看到一個問題,狀況相似以下(記爲問題1):java
public class Demo { public static void main(String[] args) { System.out.println(testInteger(1)); System.out.println(testInt(1)); } public static boolean testInteger (Integer num) { Integer[] nums = new Integer[]{1, 2, 3, 4, 5, 6}; boolean flag = Arrays.asList(nums).contains(num); return flag; } public static boolean testInt (int num) { int[] nums = new int[]{1, 2, 3, 4, 5, 6}; boolean flag = Arrays.asList(nums).contains(num); return flag; } }
結果第一個輸出爲true,提問者以爲很正常,第二個輸出卻爲false了,很奇怪。若是沒有深刻使用過該集合,或是理解泛型,在個人第一眼看來,也是有疑惑。按理來講,Java中自己針對基本類型與對應包裝類型,就有自動裝箱拆箱功能,你Integer能行,我int按理來講應該也能行。因而我大體在網上搜尋了相似的問題,發現除了上面的問題,還有相似以下的狀況(記爲問題2)數組
public class Demo2 { public static void main(String[] args) { Integer[] nums1 = new Integer[]{1, 2, 3, 4, 5, 6}; List list1 = Arrays.asList(nums1); list1.set(0, 888); System.out.println(list1); int[] nums2 = new int[]{1, 2, 3, 4, 5, 6}; List list2 = Arrays.asList(nums2); list2.set(0, 888); System.out.println(list2); } }
第一個輸出正常,集合list1中第一個元素修改成888,可是第二輸出還沒到就已經報錯,完整運行結果以下:dom
java.lang.ArrayStoreException:數組存儲異常。下面具體就集合方法與泛型探究上面的問題。ide
Integer[] nums = new Integer[]{1, 2, 3, 4, 5, 6};
Arrays.asList(nums);進入這個方法,源代碼以下:工具
public static <T> List<T> asList(T... a) { return new ArrayList<>(a); }
短短的兩行代碼,內容卻並不簡單
1.方法的參數比較特殊:參數是泛型類的,而且是可變參數。一個泛型,一個可變參數,說實話,初學者或者開發中不敢說都沒用過,但要是說有多經常使用,顯然也不符合。因此其實上面的內容在這一步就已經出問題了,下面會具體分析。
2.方法的返回值是一個ArrayList,這裏又是一個大坑,這個ArrayList並非咱們之前學習或者平時經常使用到的有序集合ArrayList,而是數組工具類Arrays類的一個靜態內部類,繼承了AbstractList,以下所示:學習
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) { //上面調用的構造方法內容即爲這裏,調用requireNonNull方法,對象array爲空的話會報空指針異常,不爲空則將其賦值給成員a。 a = Objects.requireNonNull(array); } //賦值後此時a就表明了array數組的內容,因此後面的方法基本上都圍繞a展開。 @Override public int size() { return a.length; } @Override public Object[] toArray() { return a.clone(); } ...... }
requireNonNull方法內容:優化
public static <T> T requireNonNull(T obj) { if (obj == null) throw new NullPointerException(); return obj; }
完整的過完了這一遍流程後,咱們要思考的是,經過Arrays.asList(nums)方法,咱們獲得的究竟是一個什麼。從上面的內容,咱們首先能夠肯定是一個集合。在問題1中,包裝類Integer狀況下,調用Arrays.asList(nums),基於可變參數的定義,這裏咱們至關於傳入可變參數的長度爲5。
在public static <T> List<T> asList(T... a)方法中,此時至關於泛型T指定爲Integer類型。而後private final E[] a; 此時的數組a的泛型E也相應爲Integer。此時a的內容至關於 new Integer[]{1,2,3,4,5,6}; 即一個Integer類型的一維數組,集合也隨之爲List<Integer>ui
最後獲取的是一個Arrays的一個靜態內部類ArrayList對象,在內部類中重寫了contains方法:spa
@Override public boolean contains(Object o) { return indexOf(o) != -1; }
因此在問題1中的第一種狀況傳入1時會自動轉爲對象Object類型,即上面的o,此時顯然成立,因此返回true。
那麼第二種狀況輸出false,問題出在哪裏?
在基本類型int狀況時,一樣基於可變參數的定義,同時基於java的自動轉換類型,跟上面同樣傳入可變參數的長度仍是至關於5嗎?其實不是,根本緣由在於這裏除了可變參數的定義,還有泛型的定義,可是別忘了泛型也有一些限制,首先第一點就是:
泛型的類型參數只能是類類型(包括自定義類),不能是簡單類型!
因此這裏其實就已經有分歧了,因此這裏咱們至關於傳入了一個對象,對象類型爲數組類型,可變參數的長度是1,而不是5。此時a至關於一個二維數組,在上面的狀況中,即a數組的元素類型爲數組,元素個數爲1,而不是上面的Integer,此時集合爲List<int[]>。
最後獲得的集合是這樣子:List<int[]>形式,集合長度爲1,包含了一個數組對象,而不是誤覺得的List<Intger>。
接着調用contains方法,集合中就一個數組類型對象,顯然不包含1。因此問題1中int狀況下輸出false
一樣的,在問題2的int狀況下,list2.set(0,888);即至關於將集合索引爲0的元素(即第1個,這裏集合中就一個數組元素)賦值爲一個888自動轉型的Integer類,給1個數組Array類型對象賦值Intger對象,因此才報錯了上面的數組存儲異常。指針
爲了更好的理清上面的內容,作以下擴展。
首先是Integer對象類型。
Integer[] nums = new Integer[]{1, 2, 3, 4, 5, 888}; List<Integer> list2 = Arrays.asList(nums);
System.out.println(list2.get(5));//輸出888
這裏咱們順利的經過一次索引拿到在這個888。
接着是int基本類型,前面提到了既然是可變參數,咱們傳了一個數組,一個數組也是一個對象,那咱們能夠傳入多個數組看看,以下所示。
int[] nums1 = new int[]{1, 2, 3, 4, 5, 666}; int[] nums2 = new int[]{1, 2, 3, 4, 5, 777}; int[] nums3 = new int[]{1, 2, 3, 4, 5, 888}; List<int[]> list1 = Arrays.asList(nums1,nums2,nums3); System.out.println(list1.get(2)[5]);//輸出888
這裏的get方法咱們點進去查看源代碼:
@Override public E get(int index) { return a[index]; }
因此這裏咱們輸出其實就是a[2][5],拿到888,這也印證了前面爲何說本質上就是一個二維數組的緣由,同時加深了咱們對集合與數組關係的理解。
前面重點提到這裏咱們經過Arrays.asList()方法獲得的"ArrayList",並非咱們平時經常使用的那個ArrayList,既然強調了,固然是爲了要區分,那麼不區分會有什麼問題呢,下面以簡單的Integer狀況爲例:
Integer[] nums = new Integer[]{1, 2, 3, 4, 5}; List<Integer> list = Arrays.asList(nums); list.add(888); System.out.println(list);
集合後面加個888,以爲會打印出來什麼?【1, 2, 3, 4, 5,888】?
而後事實是還沒到打印就已經拋出異常了,以下所示:
java.lang.UnsupportedOperationException:不支持的操做異常。爲何會不支持,咱們之前一直add,remove等等都沒問題。深究到底查看源代碼。
首先java.util包下的ArrayList即咱們熟知的,它的add方法實現以下:
public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; }
這也是咱們一直以來操做沒毛病的緣由。
再來看這個"ArrayLitst",它在繼承抽象類AbstractList的時候,並未實現(或者準確來講叫作重寫)add方法,因此這裏在調用add方法的時候,其實是調用的抽象類AbstractList中已經實現的add方法,咱們來看其方法內容:
public boolean add(E e) { add(size(), e); return true; }
經過add(size(), e);這個傳入2個參數的方法咱們繼續查看內容:
public void add(int index, E element) { throw new UnsupportedOperationException(); }
真相大白!throw new UnsupportedOperationException();這個異常從哪來的一目瞭然。這個"ArrayLitst"根本沒有實現add方法,因此纔會報錯。回到初始,還能想起集合與數組的種種關聯,數組自己長度就是不可變的,而這裏本質上咱們就是在操做數組,因此沒有add方法不是很正常嗎。仔細查看其類的源碼,會發現例如remove刪除等方法,也是沒有實現的,因此一樣也會報錯。
繼續探討,那麼這裏增也不行,刪也不行,那我改總行吧,就數組而言,按理來講應該是支持的,而實際狀況也的確如此。在"ArrayLitst"內部類中,其重寫了set方法,方法能將指定索引處的元素修改成指定值,同時將舊元素的值做爲返回值返回。
@Override public E set(int index, E element) { E oldValue = a[index]; a[index] = element; return oldValue; }
可是這裏還要注意一點,若是咱們在這裏針對集合修改了某處元素值,那麼原來數組的內容也會相應改變!即經過Arrays.asList()方法,獲得的集合與原數組就已經關聯起來,反之,若是咱們修改了數組內容,那麼集合獲取到的內容也會隨之改變。實踐檢驗一下:
Integer[] nums = new Integer[]{1, 2, 3, 4, 5}; List<Integer> list = Arrays.asList(nums); list.set(0, 888);//修改集合內容 nums[1] = 999;//修改數組內容 for(Integer i:nums) { System.out.println(i); } System.out.println(list);
運行後,控制檯輸出以下:
咱們發現,不管是修改數組,仍是修改集合,另外一方都會相應改變。
一開始覺得是一個小問題,漸漸的發現,其中內容很多,集合是咱們開發中算是很經常使用類庫了,良好的熟悉程度能對咱們的開發優化很多。而泛型關聯到反射等等核心內容,若是想深刻學習,也須要認真下功夫,在問題的探究中每每能有更深入的印象。