跳至主要內容

对象的深复制和浅复制


对象的深复制和浅复制

浅复制与深复制概念

浅复制(浅克隆)

被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。换言之,浅复制仅仅复制所考虑的对象,而不复制它所引用的对象。

举例说明:

常见的List的克隆方式有很多,下面我们来列举几种常见的List浅复制的方式:

public static void main(String []args){

    List<Map<String,String>> list1 = new ArrayList<Map<String, String>>();
    Map<String,String> map = new HashMap<String, String>();
    map.put("name", "xiaoming");
    map.put("age", "28");
    list1.add(map);

    //克隆方法1:利用原list1作为参数直接构造方法生成。
    List<Map<String,String>> list2 = new ArrayList<Map<String, String>>(list1);

    //克隆方法2:手动遍历将原list1中的元素全部添加到复制表中。
    for(int i = 0, l = list1.size(); i < l; i++)
        list2.add(list1.get(i));

    //克隆方法3:调用Collections的静态工具方法 Collections.copy
    //克隆方法4:使用System.arraycopy方法进行复制
}

List自身是一个对象,他在存储类类型的时候,只负责存储地址。而存储基本类型的时候,存储的就是实实在在的值。纵然你有千千万万个List,元素还是那么几个。无论是重新构造,Collections的复制方法,System的复制方法,还是手动去遍历,结果都一样,这些方法都只改变了ArrayList对象的本身,简单的添加了几个指向老元素的地址。而没做深层次的复制。(及压根没有没有 new新对象 的操作出现。)

有的时候我们确实需要将这些元素也都复制下来而不是只是用原来内存中的元素。List层实现这个问题。java语言设计之初就考虑进去了,避免操作这些埋在堆内存中的数据,所有操作都去针对能找到他们的地址。地址没了自身还会被GC干掉。所以只好一点点的去遍历,new创建新的对象并赋予原来的值。据说可能觉得上述的做法略微调整,所以巧用序列化对象让这些数据在IO流中跑了一圈,可以实现复制。其实把对象序列化到流中,java语言实在是妥协了,毕竟你不能再把地址扔进去吧?再说了io流是要和别的系统交互的,你发给别人一个地址让别人去哪个堆里找?所以不用多提肯定要新开辟堆内存的。

深复制(深克隆)之序列化

被复制对象的所有变量都含有与原来的对象相同的值,除去那些引用其他对象的变量。那些引用其他对象的变量将指向被复制过的新对象,而不再是原有的那些被引用的对象。换言之,深复制把要复制的对象所引用的对象都复制了一遍。

Java中利用串行化来做深复制(深克隆)(避免重写比较复杂对象的深复制的clone()方法,也可以程序实现断点续传等等功能)

把对象写到流里的过程是串行化(Serilization)过程,但是在Java里又非常形象地称为“冷冻”或者“腌咸菜(picking)”过程;而把对象从流中读出来的并行化(Deserialization)过程则叫做 “解冻”或者“回鲜(depicking)”过程。 应当指出的是,写在流里的是对象的一个拷贝,而原对象仍然存在于JVM里面,因此“腌成咸菜”的只是对象的一个拷贝,Java咸菜还可以回鲜。 在Java语言里深复制一个对象,常常可以先使对象实现Serializable接口,然后把对象(实际上只是对象的一个拷贝)写到一个流里(腌成咸菜),再从流里读出来(把咸菜回鲜),便可以重建对象。 如下为深复制源代码。

public List<Map<String,String>> deClone(Object obj) throws IOException,OptionalDataException,ClassNotFoundException{
    //将对象写到流里
    ByteArrayOutoutStream bo=new ByteArrayOutputStream();
    ObjectOutputStream oo=new ObjectOutputStream(bo);
    oo.writeObject(obj);//从流里读出来
    ByteArrayInputStream bi=new ByteArrayInputStream(bo.toByteArray());
    ObjectInputStream oi=new ObjectInputStream(bi);
    return(oi.readObject());
}

这样做的前提是对象以及对象内部所有引用到的对象都是可串行化的,否则,就需要仔细考察那些不可串行化的对象或属性可否设成transient,从而将之排除在复制过程之外。

请分别实现深度和浅读的对象克隆?

深度克隆和浅度克隆,Object中的克隆方法是浅度克隆。JDK规定了克隆需要满足的一些条件,简要总结一下就是:对某个对象进行克隆,对象的的成员变量如果包括引用类型或者数组,那么克隆的时候其实是不会把这些对象也带着复制到克隆出来的对象里面的,只是复制一个引用,这个引用指向被克隆对象的成员对象,但是基本数据类型是会跟着被带到克隆对象里面去的。而深度可能就是把对象的所有属性都统统复制一份新的到目标对象里面去。简单画个对比图:

???

实现方式

  • 实现Serializable接口,通过对象的序列化和反序列化实现克隆,可以实现真正的深度克隆;

  • 实现Cloneable接口并重写Object类中的clone()方法,即可实现浅度克隆。

代码

Car.java

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

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

    public String getBrand() {
        return brand;
    }

    public void setBrand(String brand) {
        this.brand = brand;
    }

    public int getMaxSpeed() {
        return maxSpeed;
    }

    public void setMaxSpeed(int maxSpeed) {
        this.maxSpeed = maxSpeed;
    }

    @Override
    public String toString() {
        return "Car{" +
                "brand='" + brand + '\'' +
                ", maxSpeed=" + maxSpeed +
                '}';
    }
}

Person.java

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public Car getCar() {
        return car;
    }

    public void setCar(Car car) {
        this.car = car;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", car=" + car +
                '}';
    }
}

Person2.java

public class Person2 implements Serializable {
    private static final long serialVersionUID = 1L;

    private String name;//姓名
    private int age;//年龄
    private Car car;//座驾

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public Car getCar() {
        return car;
    }

    public void setCar(Car car) {
        this.car = car;
    }

    @Override
    protected Object clone(){
        Person2 s= null;
        try {
            s = (Person2) Person2.super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return s;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", car=" + car +
                '}';
    }
}

CloneUtil.java

public class CloneUtil {

    private CloneUtil() {
        throw new AssertionError();
    }
    
    public static <T extends Serializable> T clone(T object) throws IOException, 
            ClassNotFoundException {
        // 说明:调用ByteArrayOutputStream或ByteArrayInputStream对象的close方法没有任何意义
        // 这两个基于内存的流只要垃圾回收器清理对象就能够释放资源,这一点不同于对外资源(如文件流)的释放
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(object);

        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bais);
        return (T) ois.readObject();
    }
}

CloneTest

public class CloneTest {
    public static void main(String[] args){
        try {
            // 深拷贝
            Person p1 = new Person("xiaoming",18,new Car("Benz",300));
            Person p2 = CloneUtil.clone(p1);
            // 修改克隆的Person对象p2关联的汽车对象的品牌属性
            // 原来的Person对象p1关联的汽车不会受到任何影响,还是Benz
            // 因为在克隆Person对象时其关联的汽车对象也被克隆了
            
            p2.setAge(25);
            p2.getCar().setBrand("BYD");
            
            System.out.println(p1);

            Person2 p3 = new Person2("xiaoming",18,new Car("Benz",300));
            Person2 p4 = (Person2) p3.clone();
            p4.setAge(25);
            p4.getCar().setBrand("BYD");

            System.out.println(p3);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

结果
???