《数据结构和Java集合框架第三版》读书笔记(五)浅复制(shallow copy)和深复制

时间:2021-07-24 09:40:21

今天学习ArrayList

一,浅复制

它的复制构造函数和clone()函数均为浅复制(shallow copy),即复制对象的引用。相反的深复制,则是复制对象的本身。

1,复制构造函数 ArrayList (Collection<? extends E> c)

得到了一个新的ArrayList对象,它包含了对c中元素的复制。但是注意,集合c的元素本质是引用,而不是对象。被引用的对象本身并没有被复制。

2,clone()函数也是如此

例子:

ArrayList<StringBuffer> temp1=new ArrayList<StringBuffer>();
StringBuffer x=new StringBuffer("yes");
temp1.add(x);
ArrayList<StringBuffer> temp2=(ArrayList)temp1.clone();
temp2.set(0,x.append("no"));


temp2中第0个元素引用的StringBuffer对象修改为“yesno”,它和temp1中第0个元素引用的是同一个StringBuffer对象 ,即是两个元素指向的还是同一内存。所以temp1和temp2均保存了“yesno”

但是

ArrayList<String> temp1=new ArrayList();
String x="yes";temp1.add(x);ArrayList<String> temp2=(ArrayList)temp1.clone();
temp2.set(0,"no");


StringBuffer对象可变,而String对象不可变。一旦创建了String对象,该对象中的内容就不能改变了。所以第二段程序中temp2的第0个元素其实是指向了新的String对象“no”,而temp1的第0个元素仍然引用“yes”

3, java.util.list.addAll()方法同样是浅复制

4,下边这个遍历list逐个元素add的方法也是浅复制,这个比较容易搞错

class Person implements Serializable{
private int age;
private String name;

public Person(){};
public Person(int age,String name){
this.age=age;
this.name=name;
}

public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}

public String toString(){
return this.name+"-->"+this.age;
}

}

List<Person> destList=new ArrayList<Person>(srcList.size());
for(Person p : srcList){
destList.add(p);
}

printList(destList);
srcList.get(0).setAge(100);
printList(destList);

s rcList的第0个元素存储的Person对象的值是可改变的。类似于StringBuffer

5,Collections的copy(List desc,List src)方法也是浅复制。下边这篇文章的说法是错误的由java中深度复制一伸出Collections.copy的使用

文章称之为深拷贝,说两个list的每个元素所指向的不是同一内存。 代码如下:

List<String> src = new ArrayList<String>();
src.add("111");
src.add("222");
src.add("333");
src.add("444");
List<String> dic = new ArrayList<String>(Arrays.asList(new String[src
.size()]));
Collections.copy(dic, src);
注意:必须用Arrays.asList方法  
public static <T> List<T> asList(T... a)返回一个受指定数组支持的固定大小的列表。

如果如下边这样写会抛出数组越界异常

List des1 = new  ArrayList( 3 );
Collections.copy(des1,src1);


ArrayList的capacity(容纳能力大小)可以指定(最好指定),而初始化时size的大小永远默认为0,只有在进行add和remove等相关操作 时,size的大小才变化。

所以因为des1.size()为0;3表示的是这个List的容纳能力为3,并不是说des1中就有了3个元素。然而进行copy()时候,首先做的是将desc1的size和src1的size大小进行比较,只有当desc1的 size 大于或者等于src1的size时才进行拷贝,否则抛出IndexOutOfBoundsException异常。


实际情形:

仍然是浅复制。误导此文作者的原因是String对象不可改变,如果仿照clone方法举得例子,改成StringBuffer就可以发现实质上还是浅复制了。


二,深复制的方法

序列化

public static <T> List<T> deepCopy(List<T> src) throws IOException, ClassNotFoundException {
ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(byteOut);
out.writeObject(src);

ByteArrayInputStream byteIn = new ByteArrayInputStream(byteOut.toByteArray());
ObjectInputStream in = new ObjectInputStream(byteIn);
@SuppressWarnings("unchecked")
List<T> dest = (List<T>) in.readObject();
return dest;
}



参考文章:一些不靠谱的java.util.List深复制方法



三,浅复制和别名是不同的。

别名是对同一个对象的另一个引用变量。如ArrayList sameList=myList;

sameList和myList是同一个ArrayList对象的引用变量,对myList对象的更改也就是对sameList对象的更改。

上述浅复制方法,如

ArrayList<E> temp2=(ArrayList)temp1.clone();

则是temp2复制了temp1中的元素,这些元素是引用变量,被复制的元素和原本的元素指向同一内存。对temp1中元素引用的对象的修改会影响到temp2中元素引用的对象;若只是将temp1中元素重新引用新的对象,指向新的内存,则temp2中元素仍然引用原本的对象、指向原本的内存。