java 拷贝

发布时间 2023-11-28 10:00:33作者: hasome

参考:https://blog.csdn.net/weiweiqiao/article/details/131490567

引用拷贝

引用拷贝会生成一个新的对象引用地址,但是两个最终指向依然是同一个对象。如何更好的理解引用拷贝呢?很简单,就拿我们人来说,通常有个姓名,但是不同场合、人物对我们的叫法可能不同,但我们很清楚哪些名称都是属于 "我" 的!
image

class Son {
 
    String name;
    int age;
 
    public Son(String name, int age) {
 
        this.name = name;
        this.age = age;
    }
}
 
public class test {
    
    public static void main(String[] args) {
 
        Son s1 = new Son("son1", 12);
        Son s2 = s1;
        s1.age = 22;
        System.out.println(s1);
        System.out.println(s2);
        System.out.println("s1的age:" + s1.age);
        System.out.println("s2的age:" + s2.age);
        System.out.println("s1==s2" + (s1 == s2));//相等
    }
}

浅拷贝

浅拷贝会创建一个新对象,新对象和原对象本身没有任何关系,新对象和原对象不等,但是新对象的属性和老对象相同。具体可以看如下区别:

  1. 如果属性是基本类型 (int,double,long,boolean 等),拷贝的就是基本类型的值;
  2. 如果属性是引用类型,拷贝的就是内存地址(即复制引用但不复制引用的对象) ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。
    image
    如何实现浅拷贝呢?也很简单,就是在需要拷贝的类上实现 Cloneable 接口并重写其 clone () 方法。
class Father{
 
    String name;
    public Father(String name) {
 
        this.name=name;
    }
 
    @Override
    public String toString() {
 
        return "Father{" +
                "name='" + name + '\'' +
                '}';
    }
}
 
class Son implements Cloneable {
 
    int age;
    String name;
    Father father;
 
    public Son(String name,int age) {
 
        this.age=age;
        this.name = name;
    }
 
    public Son(String name,int age, Father father) {
 
        this.age=age;
        this.name = name;
        this.father = father;
    }
 
    @Override
    public String toString() {
 
        return "Son{" +
                "age=" + age +
                ", name='" + name + '\'' +
                ", father=" + father +
                '}';
    }
 
    @Override
    protected Son clone() throws CloneNotSupportedException {
        // 字段father指向同一个引用。
        return (Son) super.clone();
    }
}
 
public class test {
   
    public static void main(String[] args) throws CloneNotSupportedException {
 
        Father f=new Father("bigFather");
        Son s1 = new Son("son1",13);
        s1.father=f;
        Son s2 = s1.clone();
        
        System.out.println(s1);
        System.out.println(s2);
        System.out.println("s1==s2:"+(s1 == s2));//不相等
        System.out.println("s1.name==s2.name:"+(s1.name == s2.name));//相等
        System.out.println();
 
        //但是他们的Father father 和String name的引用一样
        s1.age=12;
        s1.father.name="smallFather";//s1.father引用未变
        s1.name="son222";//类似 s1.name=new String("son222") 引用发生变化
        System.out.println("s1.Father==s2.Father:"+(s1.father == s2.father));//相等
        System.out.println("s1.name==s2.name:"+(s1.name == s2.name));//不相等
        System.out.println(s1);
        System.out.println(s2);
    }
}

深拷贝

对于上述的问题虽然拷贝的两个对象不同,但其内部的一些引用还是相同的,怎么样绝对的拷贝这个对象,使这个对象完全独立于原对象呢?就使用我们的深拷贝了。
深拷贝:在对引用数据类型进行拷贝的时候,创建了一个新的对象,并且复制其内的成员变量。
image

在具体实现深拷贝上,这里提供两个方式,重写 clone () 方法和序列法。

重写clone()

如果使用重写 clone () 方法实现深拷贝,那么要将类中所有自定义引用变量的类也去实现 Cloneable 接口实现 clone () 方法。对于字符类可以创建一个新的字符串实现拷贝。

//Father clone()方法
@Override
protected Father clone() throws CloneNotSupportedException {
   
    return (Father) super.clone();
}
 
//Son clone()方法
@Override
protected Son clone() throws CloneNotSupportedException {
   
    Son son= (Son) super.clone();//待返回克隆的对象
    son.name=new String(name);
    son.father=father.clone();
    return son;
}

序列化

  1. 如果引用数量或者层数太多了怎么办呢?不可能去每个对象挨个写 clone () 吧?那怎么办呢?借助序列化
  2. 因为序列化后:将二进制字节流内容写到一个媒介(文本或字节数组),然后是从这个媒介读取数据,原对象写入这个媒介后拷贝给 clone 对象,原对象的修改不会影响 clone 对象,因为 clone 对象是从这个媒介读取
  3. 在具体实现上,自定义的类需要实现 Serializable 接口。在需要深拷贝的类 (Son) 中定义一个函数返回该类对象:
protected Son deepClone() throws IOException, ClassNotFoundException {
   
      Son son=null;
      //在内存中创建一个字节数组缓冲区,所有发送到输出流的数据保存在该字节数组中
      //默认创建一个大小为32的缓冲区
      ByteArrayOutputStream byOut=new ByteArrayOutputStream();
      //对象的序列化输出
      ObjectOutputStream outputStream=new ObjectOutputStream(byOut);//通过字节数组的方式进行传输
      outputStream.writeObject(this);  //将当前student对象写入字节数组中
 
      //在内存中创建一个字节数组缓冲区,从输入流读取的数据保存在该字节数组缓冲区
      ByteArrayInputStream byIn=new ByteArrayInputStream(byOut.toByteArray()); //接收字节数组作为参数进行创建
      ObjectInputStream inputStream=new ObjectInputStream(byIn);
      son=(Son) inputStream.readObject(); //从字节数组中读取
      return  son;
}