现在的位置: 首页 > 综合 > 正文

深浅拷贝

2018年12月12日 ⁄ 综合 ⁄ 共 9443字 ⁄ 字号 评论关闭
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;
	}
	
}

一,浅拷贝

 1. 使用List实现类的构造方法

List<Person> destList=new ArrayList<Person>(srcList);
printList(destList);
srcList.get(0).setAge(100);
printList(destList);

2. addAll

List<Person> destList=new ArrayList<Person>();
destList.addAll(srcList);
printList(destList);
srcList.get(0).setAge(100);
printList(destList);

3. clone和System.arraycopy

 

Person[] srcPersons=srcList.toArray(new Person[0]);
Person[] destPersons=new Person[srcPersons.length];
System.arraycopy(srcPersons, 0, destPersons, 0, srcPersons.length);
//destPersons=srcPersons.clone();

printArray(destPersons);
srcPersons[0].setAge(100);
printArray(destPersons);

List<Person> destList=Arrays.asList(destPersons);
printList(destList);

System.arraycopy()方法和clone()是不能对List集合进行深复制的,改变srcPersons中某个元素的值,dstList对应的元素也会变化

注意对于数组的操作而言,system.arraycopy的操作是十分有效率的


4. Collections.copy

ArrayList<Person>
destList = new ArrayList<Person>(Arrays.asList(new Person[srcList.size()]));
Collections.copy(destList, srcList);


二,深拷贝

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;
}

ObjectOutputStream和ObjectInputStream是序列号的读写器

byteOut和byteIn是两个缓存数组



首先观察先System.arraycopy(Object src, int srcPos, Object dest, int destPos,
int length)的声明:

[java] view
plain
copy

  1. public static native void arraycopy(Object src,  int  srcPos,  
  2.                                         Object dest, int destPos,  
  3.                                         int length);  

src - 源数组。 
srcPos - 源数组中的起始位置。 
dest - 目标数组。 
destPos - 目标数据中的起始位置。 
length - 要复制的数组元素的数量。 
该方法是用了native关键字,调用的为C++编写的底层函数,可见其为JDK中的底层函数。 
再来看看Arrays.copyOf();该方法对于不同的数据类型都有相应的方法重载。 

[java] view
plain
copy

  1. //复杂数据类型  
  2. public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {  
  3.         T[] copy = ((Object)newType == (Object)Object[].class)  
  4.             ? (T[]) new Object[newLength]  
  5.             : (T[]) Array.newInstance(newType.getComponentType(), newLength);  
  6.         System.arraycopy(original, 0, copy, 0,  
  7.                          Math.min(original.length, newLength));  
  8.         return copy;  
  9.     }  
  10. public static <T> T[] copyOf(T[] original, int newLength) {  
  11.     return (T[]) copyOf(original, newLength, original.getClass());  
  12. }  

由U类型复制为T类型?
original - 要复制的数组 
newLength - 要返回的副本的长度 
newType - 要返回的副本的类型

[java] view
plain
copy

  1. //基本数据类型(其他类似byte,short···)  
  2. public static int[] copyOf(int[] original, int newLength) {  
  3.         int[] copy = new int[newLength];  
  4.         System.arraycopy(original, 0, copy, 0,  
  5.                          Math.min(original.length, newLength));  
  6.         return copy;  
  7.     }  

观察其源代码发现copyOf(),在其内部创建了一个新的数组,然后调用arrayCopy()向其复制内容,返回出去。 
总结: 
1.copyOf()的实现是用的是arrayCopy(); 
2.arrayCopy()需要目标数组,对两个数组的内容进行可能不完全的合并操作。 
3.copyOf()在内部新建一个数组,调用arrayCopy()将original内容复制到copy中去,并且长度为newLength。返回copy; 


ArrayList有一个clone方法,就是对于内部数组的每个元素浅拷贝到目标,注意,不是把这个内部数组浅拷贝到目标

Collections.copy和clone类似


class User {  
	    int age;  
	    String name;  
	  
	    public User(int a, String b) {  
	        super();  
	        this.age = a;  
	        this.name = b;  
	    }  
	    
	    public String toString() {
	    	return age +" "+name;
	    }
	    
}

public class toy {

	
	public static void main(String[] args)throws Exception {
		// TODO Auto-generated method stub
		
		ArrayList<User> ls = new ArrayList<User>(Arrays.asList(new User(1, "aa"), new User(2, "bb"), new User(3, "cc")));
		ArrayList<User> lss = (ArrayList<User>) ls.clone();
		
		lss.set(0, new User(4, "dd"));
		
		lss.get(1).age = 7;
		
		for (User u: ls) {
			System.out.println(u);
		}
		
		ArrayList<User> ls1 = new ArrayList<User>(Arrays.asList(new User[6]));
		Collections.copy(ls1, ls);
		
		ls1.set(0, new User(4, "dd"));
		
		ls1.get(1).age = 9;
		for (User u: ls) {
			System.out.println(u);
		}
		
		
		
	}

}

//output:

1 aa
7 bb
3 cc
1 aa
9 bb
3 cc


为了理解java的clone,有必要先温习以下的知识。 
java的类型,java的类型分为两大类,一类为primitive,如int,另一类为引用类型,如String,Object等等。 
java引用类型的存储,java的引用类型都是存储在堆上的。 

Java代码 

1. public class B {  

2.      int a;  

3.      String b;  

4.    

5.      public B(int a, String b) {  

6.          super();  

7.          this.a = a;  

8.          this.b = b;  

9.      }  

10. }  

对这样一个引用类型的实例,我们可以推测,在堆上它的内存存储形式(除去指向class的引用,锁的管理等等内务事务所占内存),应该有一个int值表示a,以及一个引用,该引用指向b在堆上的存储空间。 
 

为什么要clone 

恩,因为需要。废话。 
有名的GoF设计模式里有一个模式为原型模式,用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象. 
简单的说就是clone一个对象实例。使得clone出来的copy和原有的对象一模一样。 

插一个简单使用clone的例子,如果一个对象内部有可变对象实例的话,public API不应该直接返回该对象的引用,以防调用方的code改变该对象的内部状态。这个时候可以返回该对象的clone。 

问题来了,什么叫一模一样。 
一般来说,有 
x.clone()!= x 
x.clone().getClass()== x.getClass() 
x.clone().equals(x) 
但是这些都不是强制的。 
我们需要什么样的clone就搞出什么样的clone好了。 
一般而言,我们要的clone应该是这样的。copy和原型的内容一样,但是又是彼此隔离的。即在clone之后,改变其中一个不影响另外一个。 

Object的clone以及为什么如此实现 

Object的clone的行为是最简单的。以堆上的内存存储解释的话(不计内务内存),对一个对象a的clone就是在堆上分配一个和a在堆上所占存储空间一样大的一块地方,然后把a的堆上内存的内容复制到这个新分配的内存空间上。 
看例子。 

Java代码 

1.  class User {  

2.      String name;  

3.      int age;  

4.  }  

5.    

6.  class Account implements Cloneable {  

7.      User user;  

8.      long balance;  

9.    

10.     @Override  

11.     public Object clone() throws CloneNotSupportedException {  

12.         return super.clone();  

13.     }  

14. }  

 

Java代码 

1.  // user.  

2.  User user = new User();  

3.  user.name = "user";  

4.  user.age = 20;  

5.  // account.  

6.  Account account = new Account();  

7.  account.user = user;  

8.  account.balance = 10000;  

9.  // copy.  

10. Account copy = (Account) account.clone();  

11.   

12. // balance因为是基本类型,所以copy和原型是相等且独立的  

13. Assert.assertEquals(copy.balance, account.balance);  

14. copy.balance = 20000;  

15. // 改变copy不影响原型。  

16. Assert.assertTrue(copy.balance != account.balance);  

17.   

18. // user因为是引用类型,所以copy和原型的引用是同一的  

19. Assert.assertTrue(copy.user == account.user);  

20. copy.user.name = "newName";  

21. // 改变的是同一个东西,原形也跟着改变了  

22. Assert.assertEquals("newName", account.user.name);  

 

恩,默认实现是帮了我们一些忙,但是不是全部。 
primitive的确做到了相等且隔离。 
引用类型仅仅是复制了一下引用,copy和原型引用的东西是一样的。 
这个就是所谓的copy了。 
要实现深copy,即复制原型中对象的内存copy而不仅仅是一个引用。只有自己动手了。 
等等,是不是所有的引用类型都需要深copy呢? 
不是! 
我们之所以要深copy,是因为默认的实现提供的浅copy不是隔离的,换言之,改变copy的东西,会影响到原型的内部。比如例子中,改变copy的user的name,影响了原型。 
如果我们要copy的类是不可变的呢,如String,没有方法可以改变它的内部状态呢。 

Java代码 

1.  class User implements Cloneable {  

2.      String name;  

3.      int age;  

4.    

5.      @Override  

6.      public Object clone() throws CloneNotSupportedException {  

7.          return super.clone();  

8.      }  

9.  }  

 

Java代码 

1.  // user.  

2.  User user = new User();  

3.  user.name = "user";  

4.  user.age = 20;  

5.    

6.  // copy  

7.  User copy = (User) user.clone();  

8.    

9.  // age因为是primitive,所以copy和原型是相等且独立的。  

10. Assert.assertEquals(copy.age, user.age);  

11. copy.age = 30;  

12. // 改变copy不影响原型。  

13. Assert.assertTrue(copy.age != user.age);  

14.   

15. // name因为是引用类型,所以copy和原型的引用是同一的  

16. Assert.assertTrue(copy.name == user.name);  

17. // String为不可变类。没有办法可以通过对copy.name的字符串的操作改变这个字符串。  

18. // 改变引用新的对象不会影响原型。  

19. copy.name = "newname";  

20. Assert.assertEquals("newname", copy.name);  

21. Assert.assertEquals("user", user.name);  

可见,在考虑clone时,primitive不可变对象类型是可以同等对待的(都不会影响原形。 

java为什么如此实现clone呢? 
也许有以下考虑。 
1 效率和简单性,简单的copy一个对象在堆上的的内存比遍历一个对象网然后内存深copy明显效率高并且简单。 
2 不给别的类强加意义。如果A(例一中的account)实现了Cloneable,同时有一个引用指向B(例一中的user),如果直接复制内存进行深copy的话,意味着B在意义上也是支持Clone的,但是这个是在使用B的A中做的,B甚至都不知道。破坏了B原有的接口。 
3 有可能破坏语义。如果A实现了Cloneable,同时有一个引用指向B,该B实现为单例模式,如果直接复制内存进行深copy的话,破坏了B的单例模式。
4 方便且更灵活,如果A引用一个不可变对象,则内存deep copy是一种浪费。Shadow copy给了程序员更好的灵活性。 

如何clone 
clone三部曲。 
1 声明实现Cloneable接口。 
2 调用super.clone拿到一个对象,如果父类的clone实现没有问题的话,在该对象的内存存储中,所有父类定义的field都已经clone好了,该类中的primitive和不可变类型引用也克隆好了,可变类型引用都是浅copy。 
3 把浅copy的引用指向原型对象新的克隆体。 
给个例子。 

Java代码 

1.  class User implements Cloneable {  

2.      String name;  

3.      int age;  

4.    

5.      @Override  

6.      public User clone() throws CloneNotSupportedException {  

7.          return (User) super.clone();  

8.      }  

9.  }  

10.   

11. class Account implements Cloneable {  

12.     User user;  

13.     long balance;  

14.   

15.     @Override  

16.     public Account clone() throws CloneNotSupportedException {  

17.         Account account = null;  

18.   

19.         account = (Account) super.clone();  

20.         if (user != null) { //分离了对user的引用 

21.             account.user = user.clone();  

22.         }  

23.   

24.         return account;  

25.     }  

26. }  

 

对clone的态度 

clone嘛,我觉得是个好东西,毕竟系统默认实现已经帮我们做了很多事情了。 
但是它也是有缺点的。 
1 手工维护clone的调用链。这个问题不大,程序员有责任做好。 
2 如果class的field是个final的可变类,就不行了。三部曲的第三步没有办法做了。 

考虑一个类对clone的态度,有如下几种。 
1 公开支持:好吧,按照clone三部曲实现吧。前提是父类支持(公开或者默默)。 
2 默默支持:不实现Cloneable接口,但是在类里面有正确的protected的clone实现,这样,该类不支持clone,但是它的子类如果想支持的话也不妨碍。 
3 不支持:好吧,为了明确该目的,提供一个抛CloneNotSupportedException 异常的protected的clone实现。 
4 看情况支持:该类内部可以保存其他类的实例,如果其他类支持则该类支持,如果其他类不支持,该类没有办法,只有不支持。 

其他的选择 

可以用原型构造函数,或者静态copy方法来手工制作一个对象的copy。 
好处是即使class的field为final,也不会影响该方法的使用。不好的地方是所有的primitive赋值都得自己维护。 

和Serializable的比较 

使用Serializable同样可以做到对象的clone。但是: 
Cloneable本身就是为clone设计的,虽然有一些缺点,但是如果它可以clone的话无疑用它来做clone比较合适。如果不行的话用原型构造函数,或者静态copy方法也可以。 

Serializable制作clone的话,添加了太多其它的东西,增加了复杂性。 
1 所有的相关的类都得支持Serializable。这个相比支持Cloneable只会工作量更大 
2Serializable添加了更多的意义,除了提供一个方法用Serializable制作Clone,该类等于也添加了其它的public API,如果一个类实现了Serializable,等于它的2进制形式就已经是其API的一部分了,不便于该类以后内部的改动。 
3 当类用Serializable来实现clone时,用户如果保存了一个老版本的对象2进制,该类升级,用户用新版本的类反系列化该对象,再调用该对象用Serializable实现的clone。这里为了一个clone的方法又引入了类版本兼容性的问题。不划算。 


http://blog.csdn.net/shijinupc/article/details/7827507

http://will-turner.iteye.com/blog/1478194

【上篇】
【下篇】

抱歉!评论已关闭.