码迷,mamicode.com
首页 > 编程语言 > 详细

重学Java(4):浅拷贝深拷贝

时间:2016-04-13 02:07:26      阅读:171      评论:0      收藏:0      [点我收藏+]

标签:

一、为什么要使用拷贝

为了提升性能,当一个类比较大时,通过 new 新建对象的时候,是要花费巨大代价的。为了解决这个问题 Java 中提供了

Cloneable 这个借口。实现这个借口的类就具备了被拷贝的能力,而拷贝是在内存中做的,比通过 new 新建对象速度更快。

我们知道拷贝有深浅之分,而浅拷贝又有拷贝对象属性不彻底的问题。所以就有了下面的问题。

二、浅拷贝的问题

1.浅拷贝,即使用一个已知实例对新建实例的成员变量逐个赋值。当拷贝基本数据类型时,两个实例的成员变量已有存储空
间,赋值运算传值。当拷贝引用型数据时,赋值时值传递引用。这样就会造成一个问题:当修改其中某一个对象引用型数据的
  值时,其他的对象的引用型数据的值也修改了。例如下面的例子:
 1 public class Game {
 2 
 3     private String game;
 4 
 5     public Game(String game) {
 6         this.game = game;
 7     }
 8 
 9     public String play(){
10         return game;
11     }
12 
13     public void setGame(String game){
14         this.game = game;
15     }
16 }

 1 public class People implements Cloneable {
 2 
 3     private String name;
 4 
 5     private Game game;
 6 
 7     public People(String name, Game game) {
 8         this.name = name;
 9         this.game = game;
10     }
11 
12     public String getName() {
13         return name;
14     }
15 
16     public void setName(String name) {
17         this.name = name;
18     }
19 
20     public Game getGame() {
21         return game;
22     }
23 
24     public void setGame(Game game) {
25         this.game = game;
26     }
27 
28     @Override protected People clone(){
29         People people = null;
30         try {
31             people = (People) super.clone();
32         } catch (CloneNotSupportedException e) {
33             e.printStackTrace();
34         }
35         return people;
36     }
37 }
 1     public void testClone(){
 2         Game game = new Game("刺客信条");
 3 
 4         People people1 = new People("1",game);
 5         People people2 = people1.clone();
 6         people2.setName("2");
 7         People people3 = people1.clone();
 8         people3.setName("3");
 9 
10         System.out.println("name:"+people1.getName()+"game:" + people1.getGame().play());
11         System.out.println("name:"+people2.getName()+"game:" + people2.getGame().play());
12         System.out.println("name:"+people3.getName()+"game:" + people3.getGame().play());
13 
14         people3.getGame().setGame("战神");
15 
16         System.out.println("name:"+people1.getName()+"game:" + people1.getGame().play());
17         System.out.println("name:"+people2.getName()+"game:" + people2.getGame().play());
18         System.out.println("name:"+people3.getName()+"game:" + people3.getGame().play());
19     }
1 Output:
2 
3 name:1game:刺客信条
4 name:2game:刺客信条
5 name:3game:刺客信条
6 name:1game:战神
7 name:2game:战神
8 name:3game:战神

    从上面的例子可以看出,浅拷贝确实只是传了引用,当改变引用型数据的值后,指向此数据的所有引用的值也发生了改变。问题其实就是出现在 clone 这个方法上。

clone 这个方法是调用父类的 clone 方法,super.clone() 即Object 的 clone 方法。但是这个 clone 方法是有缺陷的,它并不会拷贝对象的所有属性,而是有选择性的拷贝。

其规则如下:

  a.基本类型:如果变量是基本类型,则拷贝其值。例如:int、float等。

  b.对象:如果变量是对象,则拷贝其对象引用,也就是说此时原来的对象与新对象公用该实例变量。

  c.String 字符串:如果变量是字符串,则拷贝其地址引用。但是在修改时,会从字符串池中生成新的字符串。

  通过以上规则,可以看出由于people1、people2、people3 其实都是指向同一个引用,故修改了其中一个的游戏时,其他两个也被修改了。

  这时,修改 clone 方法就可以解决这个问题,如:

 1     @Override protected People clone(){
 2         People people = null;
 3         try {
 4             people = (People) super.clone();
 5             people.setGame(new Game(people.getGame().play()));
 6         } catch (CloneNotSupportedException e) {
 7             e.printStackTrace();
 8         }
 9         return people;
10     }

  但是这样解决也会有问题,当对象非常大时,这样做会非常的麻烦。于是就有了下面的解决方法。

 

二、利用序列化实现对象的拷贝

  方法:将原始对象写入到字节流当中,再从字节流当中将其读出来,这样就可以创建一个新的对象。而且新对象与原始对象并不存在引用共享的问题。具体方法

如下:

 

 1 public class CloneUtil {
 2 
 3     public static <T extends Serializable> T clone(T object){
 4         T cloneObj = null;
 5         ByteArrayOutputStream bos = null;
 6         ObjectOutputStream oos = null;
 7         ByteArrayInputStream bis = null;
 8         ObjectInputStream ois = null;
 9         try {
10             bos = new ByteArrayOutputStream();
11             oos = new ObjectOutputStream(bos);
12             oos.writeObject(object);
13 
14             bis = new ByteArrayInputStream(bos.toByteArray());
15             ois = new ObjectInputStream(bis);
16             cloneObj = (T) ois.readObject();
17         }catch (Exception e){
18             e.printStackTrace();
19         }finally {
20             if (null != ois){
21                 try {
22                     ois.close();
23                 } catch (IOException e) {
24                     e.printStackTrace();
25                 }
26             }
27             if (null != bis){
28                 try {
29                     bis.close();
30                 } catch (IOException e) {
31                     e.printStackTrace();
32                 }
33             }
34             if (null != oos){
35                 try {
36                     oos.close();
37                 } catch (IOException e) {
38                     e.printStackTrace();
39                 }
40             }
41             if (null != bos){
42                 try {
43                     bos.close();
44                 } catch (IOException e) {
45                     e.printStackTrace();
46                 }
47             }
48         }
49         return cloneObj;
50     }
51 }

 

  使用上述方法工具类的对象需要实现 Serializable 接口,无需实现 Cloneable 接口的 clone() 方法。

 

  

 

重学Java(4):浅拷贝深拷贝

标签:

原文地址:http://www.cnblogs.com/zhangyn/p/5383979.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!