Java 拷贝

一、引用拷贝

引用拷贝是不会创建新对象的,而是创建这个对象的另一个引用,最终两个引用指向的是同一个对象

如图:

image-20220327140922612

我们通过一个实际的例子来看看:

// Student 类
public class Student {
    String name;
    int age;

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

public class test {
    public static void main(String[] args) {
        Student s1 = new Student("student1", 12);
        Student 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)); //相等
    }
}

// 运行结果
Student@1b6d3586
Student@1b6d3586
s1的age:22
s2的age:22
s1==s2 true

从运行的结果就可以看出,它们其中一个引用修改对象的值,那么另个引用的值也相应的改变

二、浅拷贝

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

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

如图所示:

image-20220327140926265

浅拷贝的实现就是在需要拷贝的类上实现 Cloneable 接口并重写其 clone() 方法。

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

我们还是用一个实际的例子看看吧:

// Person 类
class Person {
    String name;

    public Person(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Father{" +
                "name='" + name + '\'' +
                '}';
    }
}

// Student 类
class Student implements Cloneable {
    int age;
    String name;
    Person person;

    public Student(String name, int age) {
        this.age = age;
        this.name = name;
    }

    public Student(String name, int age, Person person) {
        this.age = age;
        this.name = name;
        this.person = person;
    }

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

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


public class test {
    public static void main(String[] args) throws CloneNotSupportedException {
        Person f = new Person("王冰冰");
        Student s1 = new Student("何同学", 13);
        s1.person = f;
        Student 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();

        //但是他们的person person 和String name的引用一样
        s1.age = 12;
        s1.person.name = "王冰冰"; // s1.person引用未变
        s1.name = "稚晖君"; // 这里改变了引用指向
        System.out.println("s1.person==s2.person:" + (s1.person == s2.person)); // 相等
        System.out.println("s1.name==s2.name:" + (s1.name == s2.name)); // 不相等
        System.out.println(s1);
        System.out.println(s2);
    }
}

// 运行结果
Son{age=13, name='何同学', person=Father{name='王冰冰'}}
Son{age=13, name='何同学', person=Father{name='王冰冰'}}
s1==s2:false
s1.name==s2.name:true

s1.person==s2.person:true
s1.name==s2.name:false
Son{age=12, name='稚晖君', person=Father{name='王冰冰'}}
Son{age=13, name='何同学', person=Father{name='王冰冰'}}

从运行结果可以看出,我们 clone() 了一个 student,但是 s1 和 s2 它们的引用变量指向的还是同一个对象。此时有同学可能会问:第二次运行的 s1.name == s2.name : false,因为这里 name 初始==是相等的,当我们执行 s1.name = "稚晖君" 后,会改变 s1 的 name 的指向,如图:

image-20220327140930809

小贴士:建议大家把代码复制运行一下,这样更加清楚

三、深拷贝

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

如图:

image-20211201111538912

如何实现呢?可以重写 clone() 方法和序列化

重写clone()方法

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

代码如下:

//Person 类实现 Cloneable 接口并重写 clone() 方法。student 的clone()方法需要对各个引用都拷贝一遍。

//Person clone()方法
@Override
protected Person clone() throws CloneNotSupportedException {
    return (Person) super.clone();
}
//student clone()方法
@Override
protected Student clone() throws CloneNotSupportedException {
    Student student= (Student) super.clone();//待返回克隆的对象
    student.name=new String(name);
    student.person=person.clone();
    return student;
}

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

Son{age=13, name='何同学', person=Father{name='王冰冰'}}
Son{age=13, name='何同学', person=Father{name='王冰冰'}}
s1==s2:false
s1.name==s2.name:false

s1.person==s2.person:false
s1.name==s2.name:false
Son{age=12, name='稚晖君', person=Father{name='王冰冰'}}
Son{age=13, name='何同学', person=Father{name='王冰冰'}}

从结果可以看出,s1 的 name 和 s2 的 name 不相等,s1 的 person 和 s2 的 person 也不相等,所以可以看出深拷贝是对引用数据类型也进行拷贝

序列化

序列化相信大家都不陌生吧,其实序列化是利用了深拷贝。我们只需要自定义的类实现 Serializable 接口即可。

代码如下:

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='何同学', person=Father{name='王冰冰'}}
Son{age=13, name='何同学', person=Father{name='王冰冰'}}
s1==s2:false
s1.name==s2.name:false

s1.person==s2.person:false
s1.name==s2.name:false
Son{age=12, name='稚晖君', person=Father{name='王冰冰'}}
Son{age=13, name='何同学', person=Father{name='王冰冰'}}

综上:所以序列化也是利用了深拷贝!!!

引用:

https://segmentfault.com/a/1190000038523408

https://segmentfault.com/a/1190000018646675?utm_source=sf-similar-article

posted @ 2022-06-07 19:42  Maple~  阅读(106)  评论(0编辑  收藏  举报