最近在看《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 = {1, 2, 3, 4};
        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(1,3);
        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中的值为:[1, 3, 2, 4]
        //此时的ints中的值为:[1, 2, 3, 4]
        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中的值为:[1, 3, 2, 4]
        //此时的ints中的值为:[4, 3, 2, 1]
        List<Integer> sub3 = Arrays.asList(list.get(1), list.get(2));  //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中的值为:[1, 3, 2, 4]
        //此时的ints中的值为:[4, 3, 2, 1]
        List<Integer> sub4 = new ArrayList<Integer>(list.subList(1,3)); //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<Integer> sub = list.subList(1,3);

上面代码的内存模型应该是下面这个样子(下图不够准确):
这里写图片描述
由此可见sub与list指向同一块内存。所以,对sub内容的改变会影响list。


下面代码:

import java.util.*;

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

其字节码如下所示:

public static void main(java.lang.String[]);
  Code:
   Stack=4, Locals=3, Args_size=1
   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
   11:  dup
   12:  iconst_1
   13:  iconst_2
   14:  invokestatic    #3; //Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
   17:  aastore
   18:  dup
   19:  iconst_2
   20:  iconst_3
   21:  invokestatic    #3; //Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
   24:  aastore
   25:  dup
   26:  iconst_3
   27:  iconst_4
   28:  invokestatic    #3; //Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
   31:  aastore
   32:  astore_1
   33:  new #4; //class java/util/ArrayList
   36:  dup
   37:  invokespecial   #5; //Method java/util/ArrayList."<init>":()V
   40:  astore_2
   41:  aload_2
   42:  aload_1
   43:  invokestatic    #6; //Method java/util/Collections.addAll:(Ljava/util/Collection;[Ljava/lang/Object;)Z
   46:  pop
   47:  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]
这里写图片描述
解释如下:
参考的资料: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的顺序问题(二)

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐