Java实现克隆的几种方式

  1. 实现Cloneable接口,重写Object类中的clone()方法
  2. 实现Serializable接口,通过对象的序列化和反序列化实现克隆

实现Cloneable接口演示

public class Student implements Cloneable {
    
    private String name;//姓名
    private int age;//年龄

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
    
    //setter and getter
	...
	}

测试方法

public class TestClone {

    public static void main(String[] args) {
        Student s1 = new Student();//创建学生对象
        s1.setName("小麦");
        s1.setAge(12);
      
        try {
            Student s2 =(Student) s1.clone();//调用重写后的clone()方法进行对象克隆
           
            System.out.println(s1);
            System.out.println(s2);
            System.out.println(s1==s2);//结果为false
        } catch (CloneNotSupportedException e) {
      
            e.printStackTrace();
        }
    }
}

可以看出克隆的结果确实是实现了克隆对象,但在测试方法中加入如下代码

System.out.println(s1.getName().hashCode());//771191
    System.out.println(s2.getName().hashCode());//771191

我的输出结果为771191,说明两个对象的name属性都指向了同一个对象,并没有新创建对象,这实际上就是典型的浅克隆

深克隆实现演示

实体类Person代码,实现Serializable接口

public class Person implements Serializable {

    private static final long serialVersionUID = 1L;
    private String name;//姓名
    private int age;//年龄
    private Car car;

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

   //setter and getter
   ...
   
}

克隆工具类

public class CloneUtil {

    private CloneUtil(){
        throw new AssertionError();
    }

    @SuppressWarnings("unchecked")
    public static <T extends Serializable> T clone(T obj) throws Exception{
       
        ByteArrayOutputStream bout = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bout);
        oos.writeObject(obj);
        
        ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bin);
        
        return (T) ois.readObject();
    }
    //ByteArrayInputStream和ByteArrayOutputStream无需调用close方法,是基于内存的流,只要垃圾回收器清理对象就能够释放资源

}

实体类Car,实现Serializable接口

public class Car implements Serializable {
   
    private static final long serialVersionUID = 11L;
    private String brand;//品牌
    private int maxSpeed;//最高时速

    public Car(String brand, int maxSpeed) {
        this.brand = brand;
        this.maxSpeed = maxSpeed;
    }

	//setter and getter
	...
}

测试类

public class CloneTest {
    public static void main(String[] args) {
       
        Person p1 = new Person("小东",12,new Car("Benz",10));
        try {
            Person p2 = CloneUtil.clone(p1);
            p2.getCar().setBrand("BYD");
            
            System.out.println(p1.getCar().hashCode());//13648335
            System.out.println(p2.getCar().hashCode());//1935637221
            
            //修改克隆的Person对象p2关联的的汽车对象的品牌属性
            //原来的Person对象p1关联的汽车不会受到影响
            //因为在克隆Person对象时关联的汽车对象也被克隆了
       
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

这里是通过序列化和反序列化的方式实现深克隆,要克隆的对象要实现Serializable接口,测试方法中通过获取两个对象中Car属性的hashCode可知这种方式实现的克隆不止Person对象新创建了一份,属性同样也是新创建的,也就是两个对象中的属性都已相对独立。

其实也可以通过在浅克隆重写的clone()方法中,实现属性的clone()操作,以上述为例,Person类中的Car同样可以实现新创建对象的效果,两种方式实现深克隆均非真正意义上的深克隆,原因是Car对象中的brand最终指向的仍会是同一个对象,要实现真正意义上的深克隆几乎不可能实现。

写到这里有人可能会说:“你用两个例子都有很多不一样,怎么能确定第一种方式的克隆去克隆Person实例,返回的Car对象是同一个?”

所以我在第一个例子中的测试类中加入如下代码

Person p1 = new Person("haha",12,new Car("hehe",122));
    Person p2 = (Person)p1.clone();
  	System.out.println(p1);
    System.out.println(p2);
    System.out.println(p1.getCar().hashCode());//984849465
    System.out.println(p2.getCar().hashCode());//984849465

结果显示,属性值的哈希值确实相同,也就是没有创建新实例。

纪念一下自己的第一篇博文。