Java对象的浅克隆和2种深克隆的使用

我们要获得一个对象的副本时,用new创建对象再对各个域赋值,还不如直接对象克隆。Java的对象克隆有浅克隆和深克隆之分。但是不管怎么样,我们都要实现Object类的clone()方法。

Object类的clone()方法

当需要克隆对象时,需要使用Object类的clone()方法,该方法的声明如下:

  1. protected native Object clone() throws CloneNotSupportedException;

通常我们继承Cloneable接口实现这个方法,我们要把这个方法的权限变成public。

浅克隆
我们编写2个类,分别是Dog和Cat,在Cat中声明Dog,然后我们克隆Cat,发现Dog没有被克隆,这就是浅克隆,不能克隆引用对象。
代码:

  1. public class Dog {
  2. static final long serialVersionUID = 1L;
  3. private String name;
  4. private String age;
  5. public Dog(String name, String age) {
  6. this.name = name;
  7. this.age = age;
  8. }
  9. //.....set和get方法
  10. }

Cat:

  1. public class Cat implements Cloneable {
  2. private Dog d;
  3. private String name;
  4. public Cat(Dog d, String name) {
  5. this.d = d;
  6. this.name = name;
  7. }
  8. //.....set和get方法
  9. @Override
  10. public Cat clone(){
  11. Cat cat=null;
  12. try{
  13. cat=( Cat)super.clone();
  14. }catch (Exception e){
  15. e.printStackTrace();
  16. }
  17. return cat;
  18. }
  19. }

测试:

  1. public class test {
  2. public static void main (String arg[]) throws Exception{
  3. Dog d=new Dog("xiao","16");
  4. Cat c=new Cat(d,"ming");
  5. Cat c1=c.clone();
  6. System.out.println("2个对象是否相同:");
  7. System.out.println(c1==c);
  8. System.out.println("改变c1,看看会影响c对象吗?");
  9. c1.setName("c1的ming");
  10. c1.getD().setAge("c1的16");
  11. System.out.println("输出c:");
  12. System.out.println("name:"+c.getName());
  13. System.out.println("Dog的age:"+c.getD().getAge());
  14. }
  15. }

结果:

  1. 2个对象是否相同:
  2. false
  3. 改变c1,看看会影响c对象吗?
  4. 输出c:
  5. nameming
  6. Dogagec116

我们从结果看出:这2个对象是不一样的,改变克隆对象的name不能改变c的对象,说明c1和c对象的name不一样,但是我们改变c1的Dog,也改变了c的Dog对象,说明克隆对象中的引用对象和被克隆对象中的引用对象是同一个。则这就是浅克隆,只能克隆除了基本类型和String的域,而引用对象还是同一个。

Java对象的深克隆
正如浅克隆不能克隆引用对象,如果连引用对象都能克隆,这就是深克隆。对于深克隆,我们有2种方法,分别:将引用类型也进行克隆或者用序列化反序列化进行克隆。

将引用类型也进行深克隆

对于上面的浅克隆,我们只要将Dog也克隆就行
Dog类

  1. public class Dog implements Cloneable{
  2. private String name;
  3. private String age;
  4. public Dog(String name, String age) {
  5. this.name = name;
  6. this.age = age;
  7. }
  8. //.....set和get方法
  9. @Override
  10. public Dog clone(){
  11. Dog dog=null;
  12. try{
  13. dog=( Dog)super.clone();
  14. }catch (Exception e){
  15. e.printStackTrace();
  16. }
  17. return dog;
  18. }
  19. }

Cat类:

  1. public class Cat implements Cloneable {
  2. private Dog d;
  3. private String name;
  4. public Cat(Dog d, String name) {
  5. this.d = d;
  6. this.name = name;
  7. }
  8. //.....set和get方法
  9. @Override
  10. public Cat clone(){
  11. Cat cat=null;
  12. try{
  13. cat=( Cat)super.clone();
  14. cat.d= d.clone();
  15. }catch (Exception e){
  16. e.printStackTrace();
  17. }
  18. return cat;
  19. }
  20. }

测试:

  1. public class test {
  2. public static void main (String arg[]) throws Exception{
  3. Dog d=new Dog("xiao","16");
  4. Cat c=new Cat(d,"ming");
  5. Cat c1=c.clone();
  6. System.out.println("2个对象是否相同:");
  7. System.out.println(c1==c);
  8. System.out.println("改变c1,看看会影响c对象吗?");
  9. c1.setName("c1的ming");
  10. c1.getD().setAge("c1的16");
  11. System.out.println("输出c:");
  12. System.out.println("name:"+c.getName());
  13. System.out.println("Dog的age:"+c.getD().getAge());
  14. }
  15. }

结果:

  1. 2个对象是否相同:
  2. false
  3. 改变c1,看看会影响c对象吗?
  4. 输出c:
  5. nameming
  6. Dogage16

我们从结果看出:这2个对象是不一样的,重点在于我们改变了c1的Dog的值没有影响c的Dog。如果引用类型中还有可变的引用类型域的话,则该域也是需要克隆的,如Cat类的d,也是要克隆的。

用序列化反序列化进行深克隆
对于上面的深克隆方法来说,如果一二个引用对象并不复杂,但是如果很多引用对象,就要一 一的克隆,这就很麻烦了,于是有了序列化反序列化进行深克隆。

序列化可以把对象变成二进制流写入文件中,然后用反序列化读出对象就是一个新的对象了。当然对于克隆我们不需要保存,所以我们保存在二进制流数组中就行了。对于序列化和反序列化的文章可以看这篇:Java序列化和反序列化与transient关键字

代码:
Dog:

  1. public class Dog implements Serializable{
  2. private static final long serialVersionUID = 42L;
  3. private String name;
  4. private String age;
  5. public Dog(String name, String age) {
  6. this.name = name;
  7. this.age = age;
  8. }
  9. //.....set和get方法
  10. }

Cat:

  1. public class Cat implements Cloneable, Serializable {
  2. private static final long serialVersionUID = 42L;
  3. private Dog d;
  4. private String name;
  5. public Cat(Dog d, String name) {
  6. this.d = d;
  7. this.name = name;
  8. }
  9. //.....set和get方法
  10. @Override
  11. public Cat clone(){
  12. Cat cat=null;
  13. ByteArrayOutputStream bs=new ByteArrayOutputStream();
  14. try{
  15. ObjectOutputStream out=new ObjectOutputStream(bs);
  16. out.writeObject(this);
  17. out.close();
  18. }catch (Exception e){
  19. e.printStackTrace();
  20. }
  21. ByteArrayInputStream is=new ByteArrayInputStream(bs.toByteArray());
  22. try{
  23. ObjectInputStream in=new ObjectInputStream(is);
  24. cat=(Cat) in.readObject();
  25. in.close();
  26. }catch (Exception e){
  27. e.printStackTrace();
  28. }
  29. return cat;
  30. }
  31. }

测试:

  1. public class test {
  2. public static void main (String arg[]) throws Exception{
  3. Dog d=new Dog("xiao","16");
  4. Cat c=new Cat(d,"ming");
  5. Cat c1=c.clone();
  6. System.out.println("2个对象是否相同:");
  7. System.out.println(c1==c);
  8. System.out.println("改变c1,看看会影响c对象吗?");
  9. c1.setName("c1的ming");
  10. c1.getD().setAge("c1的16");
  11. System.out.println("输出c:");
  12. System.out.println("name:"+c.getName());
  13. System.out.println("Dog的age:"+c.getD().getAge());
  14. }
  15. }

结果:

  1. 2个对象是否相同:
  2. false
  3. 改变c1,看看会影响c对象吗?
  4. 输出c:
  5. nameming
  6. Dogage16

我们从结果看出:这2个对象是不一样的,重点在于我们改变了c1的Dog的值没有影响c的Dog。对于任何一个序列化的对象,都是要求实现Serializable接口,如果该对象中还有引用对象,这个引用对象也要实现Serializable接口,但是这个方法会比上面的方法慢