天天看點

子list中的順序會影響list的順序問題

最近在看《Thinking in Java》中關于容器的章節(第11章 持有對象),有一個例子發現subList中資料順序的改變會影響原list中資料的順序。下面總結如下。

結論

  • 使用List.subList方法得到的子序列其實隻是将指針指向了原list,并設定了這個sub list的大小。
  • 使用Arrays.asList(數組的引用)這種方式生成連結清單時,該連結清單仍然指向這個資料,是以,對這個連結清單中資料順序的改變會影響原數組中元素的順序。

測試代碼

package org.fan.learn.shuffle;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

/**
 * Created by fan on 16/3/20.
 */
public class Main {
    public static void main(String[] args) {
        Integer[] ints = {, , , };
        List<Integer> list = new ArrayList<Integer>();
        Collections.addAll(list, ints);  //list與ints是兩個不同的記憶體區域
        System.out.println(list);  //list: [1, 2, 3, 4]
        System.out.println(Arrays.toString(ints)); //ints: [1, 2, 3, 4]

        List<Integer> sub = list.subList(,);
        System.out.println("sub before reverse : " + sub); //sub before reverse : [2, 3]
        Collections.reverse(sub);
        System.out.println("sub after reverse : " + sub);  //sub after reverse : [3, 2]
        System.out.println("list : " + list);  //會影響list:[1, 3, 2, 4]
        System.out.println(Arrays.toString(ints));//不會影響ints: [1, 2, 3, 4]

        //此時的list中的值為:[, , , ]
        //此時的ints中的值為:[, , , ]
        List<Integer> sub2 = Arrays.asList(ints);   //sub2 與 ints指向的是同一塊記憶體
        System.out.println("sub2 before reverse : " + sub2); //sub2 before reverse : [1, 2, 3, 4]
        Collections.reverse(sub2);  //
        System.out.println("sub2 after reverse : " + sub2);  //sub2 after reverse : [4, 3, 2, 1]
        System.out.println("list : " + list);  //list : [1, 3, 2, 4]
        System.out.println(Arrays.toString(ints));  //[4, 3, 2, 1]

        //此時的list中的值為:[, , , ]
        //此時的ints中的值為:[, , , ]
        List<Integer> sub3 = Arrays.asList(list.get(), list.get());  //sub3不會影響list
        System.out.println("sub3 before reverse : " + sub3); //sub3 before reverse : [3, 2]
        Collections.reverse(sub3);  //
        System.out.println("sub3 after reverse : " + sub3);  //sub3 after reverse : [2, 3]
        System.out.println("list : " + list);  //list : [1, 3, 2, 4]
        System.out.println(Arrays.toString(ints));  //[4, 3, 2, 1]

        //此時的list中的值為:[, , , ]
        //此時的ints中的值為:[, , , ]
        List<Integer> sub4 = new ArrayList<Integer>(list.subList(,)); //sub4不會影響list
        System.out.println("sub4 before reverse : " + sub4); //sub4 before reverse : [3, 2]
        Collections.reverse(sub4);  //
        System.out.println("sub4 after reverse : " + sub4);  //sub4 after reverse : [2, 3]
        System.out.println("list : " + list);  //list : [1, 3, 2, 4]
        System.out.println(Arrays.toString(ints));  //[4, 3, 2, 1]
    }

}
           

分析

上面代碼的記憶體模型應該是下面這個樣子(下圖不夠準确):

子list中的順序會影響list的順序問題

由此可見sub與list指向同一塊記憶體。是以,對sub内容的改變會影響list。

下面代碼:

import java.util.*;

public class TestList {
    public static void main(String[] args) {
        Integer[] ints = {, , , };
        List<Integer> list = new ArrayList<Integer>();
        Collections.addAll(list, ints);
    }
}
           

其位元組碼如下所示:

public static void main(java.lang.String[]);
  Code:
   Stack=, Locals=, Args_size=
   :   iconst_4
   :   anewarray   #2; //class java/lang/Integer
   4:   dup
   :   iconst_0
   :   iconst_1
   :   invokestatic    #3; //Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
   10:  aastore
   :  dup
   :  iconst_1
   :  iconst_2
   :  invokestatic    #3; //Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
   17:  aastore
   :  dup
   :  iconst_2
   :  iconst_3
   :  invokestatic    #3; //Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
   24:  aastore
   :  dup
   :  iconst_3
   :  iconst_4
   :  invokestatic    #3; //Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
   31:  aastore
   :  astore_1
   :  new #4; //class java/util/ArrayList
   36:  dup
   :  invokespecial   #5; //Method java/util/ArrayList."<init>":()V
   40:  astore_2
   :  aload_2
   :  aload_1
   :  invokestatic    #6; //Method java/util/Collections.addAll:(Ljava/util/Collection;[Ljava/lang/Object;)Z
   46:  pop
   :  return

           

1.由此可見,對于整型常亮1 2 3 4,在java位元組碼中直接使用iconst_X(X代表具體整型值)來表示。

2.而且在new Integer數組時,直接指定了數組的大小,如下所示:

0:   iconst_4
   1:   anewarray   #2; //class java/lang/Integer
           

是以,數組是不可以擴容的。

3.當1 2 3 4放入Integer數組時,有一個類型轉換的過程,如下所示:

6:   iconst_1
   7:   invokestatic    #3; //Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
           

這就是所謂的自動裝箱(Autoboxing)。

update-20160523

寫這邊博文到現在已經過去了一段時間,今日看到有博樂推薦我這篇博文,很是感激。于是又看了一遍,發現有些點又有些生疏了。下面記錄如下:

關于aastore

第一個a表示數組中存放的資料類型,a是reference的意思。

第二個a表示array,表示這是一個數組操作。

store就是将元素存入數組了。

aastore:

Description: store value in array[index]

子list中的順序會影響list的順序問題

解釋如下:

參考的資料:aastore

在使用這個aastore時,必須要先入棧數組變量,然後入棧下标index,然後入棧所需要存放的值。

如在這個例子中:

0:   iconst_4
   1:   anewarray   #2; //class java/lang/Integer
   4:   dup
   5:   iconst_0
   6:   iconst_1
   7:   invokestatic    #3; //Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
   10:  aastore
           

首先new出數組變量ints,然後dup一下,複制這個數組變量并入棧,此時的棧中有兩個數組變量ints。然後入棧常量0,表示要操作的數組ints的下标。然後入棧常量1,表示要往數組中存放的資料。由于ints中存放的是封裝類型Integer,是以需要将int類型的1,自動轉型成Integer類型的。這時棧中的資料從棧底到棧頂依次是:ints,ints,0(int型),1(Integer型)。然後調用aastore,将Integer類型的1存入下标為0的ints中。這時棧中隻有一個ints。

雖然更新了一次,但是感覺本質問題(影響順序)沒有解釋的清清楚楚,是以又寫了一篇博文闡述:子list中的順序會影響list的順序問題(二)