引用拷贝,浅拷贝和深拷贝

2022/4/7 6:23:25

本文主要是介绍引用拷贝,浅拷贝和深拷贝,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

1.引用拷贝

引用拷贝会生成一个新的对象引用地址,但是两个最终指向依然是同一个对象。

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的引用
        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));//相等
    }
}

2.浅拷贝

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

  • 如果属性是基本类型(int,double,long,boolean等),拷贝的就是基本类型的值;
  • 如果属性是引用类型,拷贝的就是内存地址(即复制引用但不复制引用的对象),因此如果其中一个对象改变了这个地址,就会影响到另一个对象。

如何实现浅拷贝呢?也很简单,就是在需要拷贝的类上实现Cloneable接口并重写其clone()方法。

@Override
protected Object clone() throws CloneNotSupportedException {
  return super.clone();
}

在使用的时候直接调用类的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 {
        return (Son) super.clone();
    }
}

public class Demo {
    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);
    }
}
View Code

这种浅拷贝除了对象本身不同以外,各个零部件和关系和拷贝对象都是相同的,就好像双胞胎一样,是两个人,但是其开始的样貌、各种关系(父母亲人)都是相同的。

需要注意的是其中name初始==是相等的,是因为初始浅拷贝它们指向一个相同的String,而后s1.name="son222" 则改变引用指向。

3.深拷贝

对于上述的问题虽然拷贝的两个对象不同,但其内部的一些引用还是相同的,怎么样绝对的拷贝这个对象,使这个对象完全独立于原对象呢?

就使用我们的深拷贝了。深拷贝:在对引用数据类型进行拷贝的时候,创建了一个新的对象,并且复制其内的成员变量。

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

重写clone()方法

如果使用重写clone()方法实现深拷贝,那么要将类中所有自定义引用变量的类也去实现Cloneable接口实现clone()方法。

对于字符类可以创建一个新的字符串实现拷贝。

对于上述代码,Father类实现Cloneable接口并重写clone()方法。son的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;
}

其他代码不变,执行结果如下:

Son{age=13, name='son1', father=Father{name='bigFather'}}
Son{age=13, name='son1', father=Father{name='bigFather'}}
s1==s2:false
s1.name==s2.name:false

s1.Father==s2.Father:false
s1.name==s2.name:false
Son{age=12, name='son222', father=Father{name='smallFather'}}
Son{age=13, name='son1', father=Father{name='bigFather'}}

序列化

可以发现这种方式实现了深拷贝。但是这种情况有个问题,如果引用数量或者层数太多了怎么办呢?

不可能去每个对象挨个写clone()吧?那怎么办呢?借助序列化。

因为序列化后:将二进制字节流内容写到一个媒介(文本或字节数组),然后是从这个媒介读取数据,

原对象写入这个媒介后拷贝给clone对象,原对象的修改不会影响clone对象,因为clone对象是从这个媒介读取。

熟悉对象缓存的知道我们经常将Java对象缓存到Redis中,然后还可能从Redis中读取生成Java对象,这就用到序列化和反序列化。

一般可以将Java对象存储为字节流或者json串然后反序列化成Java对象。

因为序列化会储存对象的属性但是不会也无法存储对象在内存中地址相关信息。所以在反序列化成Java对象时候会重新创建所有的引用对象。

在具体实现上,自定义的类需要实现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;
}

其他不变,运行结果为:

Son{age=13, name='son1', father=Father{name='bigFather'}}
Son{age=13, name='son1', father=Father{name='bigFather'}}
s1==s2:false
s1.name==s2.name:false

s1.Father==s2.Father:false
s1.name==s2.name:false
Son{age=12, name='son222', father=Father{name='smallFather'}}
Son{age=13, name='son1', father=Father{name='bigFather'}}

 



这篇关于引用拷贝,浅拷贝和深拷贝的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程