一、为什么要使用拷贝 为了提升性能,当一个类比较大时,通过 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() 方法。
时间: 2024-10-14 14:23:57