在Arrays.asList()引發的問題中進一步學習集合與泛型等內容,arrays.aslist

來源:互聯網
上載者:User

在Arrays.asList()引發的問題中進一步學習集合與泛型等內容,arrays.aslist
  前言

  最近在網上看到一個問題,情況類似如下(記為問題1):

    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,但是第二輸出還沒到就已經報錯,完整運行結果如下:

  java.lang.ArrayStoreException:數組儲存異常。下面具體就集合方法與泛型探究上面的問題。

  過程

  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為空白的話會報null 指標異常,不為空白則將其賦值給成員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>

最後擷取的是一個Arrays的一個靜態內部類ArrayList對象,在內部類中重寫了contains方法:

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

  運行後,控制台輸出如下:

  我們發現,不論是修改數組,還是修改集合,另一方都會相應改變。

   小結

  一開始以為是一個小問題,漸漸的發現,其中內容不少,集合是我們開發中算是很常用類庫了,良好的熟悉程度能對我們的開發最佳化不少。而泛型關聯到反射等等核心內容,如果想深入學習,也需要認真下功夫,在問題的探究中往往能有更深刻的印象。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.