顯示具有 ArrayList 標籤的文章。 顯示所有文章
顯示具有 ArrayList 標籤的文章。 顯示所有文章

2013年11月5日 星期二

ArrayList 使用 toarray 轉換成物件陣列

     在某些情況下,我們希望能夠將 ArrayList 物件所存的內容轉換成陣列的形式,在 ArrayList 中提供了 toarray 方法來達到我們的需求。下面是 Java 所提供的 toarray 方法的原始碼:

    public Object[] toArray() {
        return Arrays.copyOf(elementData, size);
    }

    public T[] toArray(T[] a) {
        if (a.length < size)
            // Make a new array of a's runtime type, but my contents:
            return (T[]) Arrays.copyOf(elementData, size, a.getClass());
        System.arraycopy(elementData, 0, a, 0, size);
        if (a.length > size)
            a[size] = null;
        return a;
    }

     有上面可知,我們有兩個方式可以使用 toarray 方法,一個是不帶任何參數 toarray 方法回傳 Object 陣列,另外一個是回傳你所傳入的型態陣列。
     我們先來看 public T[] toArray(T[] a) 的方法,從原始碼中,我們發現此方法會使用參數的 length 的屬性,代表該參數必須要先配置過記憶體才行,如果直接傳 null 給它會引發 "java.lang.NullPointerException" 的例外事件。再回頭看一下原始碼,會發現當你所配置的陣列元素小於目前 ArrayList 所儲存的元素時,toarray 方法會幫你配置足夠的元素來儲存目前 ArrayList 物件中所含的所有元素。而當你的所配置的陣列元素個數大於目前會將你所配置的第 size 元素設定為null。基本上你可以想成超出的元素都會被設置成 null 就可以。(PS: 要使用 ArrayList 其型態一定是參考型態,如果使用基本型態會出現編譯失敗 ,而物件陣列的預設值為 null)。

    另外一個 toArray法是回傳 Object 陣列,由於兩個陣列基底型別不同時,不能互相參照,所以你無法直接將回傳的 Object 陣列轉成你要的型態陣列。但是你可以針對每個 Object 元素做轉型。

範例程式如下:

     static void toArrayDemo() {
        
        ArrayList<integer> list = new ArrayList<integer>(){{
            add(1);
            add(2);
            add(3);
            add(4);
        }};

        //"main" java.lang.NullPointerException
        //Integer[] test = null; 
        //test = list.toArray(test); 
        // or Integer[] test = list.toArray(null);
        
        // 配置的元素小於 ArrayList 所存的元素各數少時,
        // toArray 會幫你重新配置足夠的陣列個數來儲存。
        Integer[] test = list.toArray(new Integer[0]);
        System.out.print("<1>[ ");
        for (Integer val : test) {
            System.out.print(val + " ");
        }
        System.out.println("]");
        // 配置大於 ArrayList 所存的 元素個數,多餘的陣列元素都是 null
        Integer[] test2 = list.toArray(new Integer[10]);
        System.out.print("<2>[ ");
        for (Integer val : test2) {
            System.out.print(val + " ");
        }
        
        System.out.println("]");
        
        // 配置符合目前 ArrayList 所含的元素個數
        Integer[] test3 = list.toArray(new Integer[list.size()]);
        System.out.print("<3>[ ");
        for (Integer val : test3) {
            System.out.print(val + " ");
        }
        System.out.println("]");
        
        //無法將 Object 陣列轉成其他型態的陣列
        //[Ljava.lang.Object; cannot be cast to [Ljava.lang.Integer;
        //Integer[] test4 = (Integer[] )list.toArray();
        
        Object [] obj = list.toArray();
        
        System.out.print("<4>[ ");
        for (Object val : obj) {
            // 你可以對每一個 Object 中的元素轉型成 Integer 型態的物件
            // 因為我們宣告的 ArrayList 是儲存 Integer 物件,所以每一個
            // Object 物件都是參考到  Integer 物件
            
            System.out.print( ((Integer)val).intValue() + " ");
            // 下面方式與上面轉型所列印出來的結果相同,因為每一個 Object 元素
            // 都是參考到 Integer 物件,而 Integer 的 toString method 就是列印其值。
            //System.out.print( val + " ");
        }
        System.out.println("]");
    }

輸出結果:
<1>[ 1 2 3 4 ]
<2>[ 1 2 3 4 null null null null null null ]
<3>[ 1 2 3 4 ]
<4>[ 1 2 3 4 ]

補充:
    Object[] obj = new Object[size] 與 Object[] intObj = new Integer[2]  的不同是,obj 陣列可以指定
任何型態的物件參考,每一個元素值不一定要儲存相同的物件。而 intObj 是參考到 Integer 陣列所以其元素只能儲存 Integer 物件,儲存其他型態物件會引發 "java.lang.ArrayStoreException" 例外事件。另外 intObj 是參考到 Integer 陣列,所以你可以將 intObj 指定給其他 Integer 陣列來參考。

範例程式如下:

    static void arrayDemo() {
        Object [] intobj = new Integer[2];
        System.out.println("obj instance Integer[] = " + (intobj instanceof Integer[]));
        obj[0] = new Integer(10);
        //Exception in thread "main" java.lang.ArrayStoreException: java.lang.Float
        //obj[1] = new Float(10.f);
        
        Object [] obj2 = new Object[2];
        System.out.println("obj2 instance Integer[] = " + (obj2 instanceof Integer[]));
        obj2[0] = new Integer(10);
        obj2[1] = new Float(10.f);
        System.out.println("obj2[0] = " + obj2[0] + " , obj2[1] = " + obj2[1]);
    }

2013年10月31日 星期四

ArrayList 移除 element 的問題

    今天查了 ArrayList 用法的時候,發現在 Javaworld 有人發了一篇 arraylist的移除問題,下面是他的 Sample Code
 
   ArrayList<integer> list = new ArrayList<integer>();
   list.add(2);
   list.add(2);
   list.add(3);
   list.add(1);
   list.add(2);
   System.out.println(list);

   for (int i=0; i<list.size(); i++){
      if(list.get(i)<3) {
         list.remove(i);
      }
   }
   System.out.println(list);

     原發問者想要將小於 3 的值全部移掉只剩下 3 這個元素,但是卻印出 [1 , 2],上面的的Sample Code 我有修改過,所以顯示為 [2,3,2] ,因為原程式在判斷條件小於3 的 if 後面多了分號 Orz。
     Anyway, 回到原來的問題,當你刪除一個元素時 size 也會跟著縮小,但是 i 的值卻會遞增,在 for 迴圈最後面加入除錯訊息,就可以知道為何印出 [2, 3, 2]。下面是我在 for 迴圈中加入除錯訊息所印出的資料:
i = 0 : [2, 3, 1, 2] size:4
i = 1 : [2, 3, 1, 2] size:4
i = 2 : [2, 3, 2] size:3

    要解決這個問題有下面三種方式

  1. 透過 Iterator 
  2. 透過 for 迴圈反向檢查,可以在 ConcurrentModificationException for ArrayList 中查到方式。
  3. 透過 CopyOnWriteArrayList

透過 Iterator 方式如下:
        
        Iterator<Integer> Allelement = list.iterator();
        while (Allelement.hasNext()) {
            if (Allelement.next() < 3) {
                Allelement.remove(); // 刪除最後 Iterator 所回傳的值,也就是目前所指到值
            }
        }
透過 for 迴圈反向檢查:
        
       for (int i = list.size() - 1; i >= 0; i--) {
            if(list.get(i) < 3){
                list.remove(i);
            }
        }

透過 CopyOnWriteArrayList:
        CopyOnWriteArrayList <Integer> list = 
                     new CopyOnWriteArrayList <Integer>();
        list.add(2);
        list.add(2);
        list.add(3);
        list.add(1);
        list.add(2);
        for (Integer val : list) {
            if (val  < 3) {
                list.remove(val);
            }
        }
請注意,使用 foreach 去詢訪 ArrayList 時,你不可以在 foreach 中 執行 remove,如果執行了應該會出現 java.util.ConcurrentModificationException 的例外訊息。