Java深浅拷贝介绍

浅拷贝

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

原理示意图:

 

要实现对象浅拷贝还是比较简单的,只需要被复制类需要实现 Cloneable 接口,重写 clone 方法即可,对 User类进行改造,使其可以支持浅拷贝。

代码示例:

package main.java.cn.test;

/**
 * @author ningzhaosheng
 * @date 2023/6/11 10:04:25
 * @description
 */
public class User implements Cloneable{

    // 姓名
    private String name;
    // 年龄
    private int age;
    // 邮件
    private String email;
    // 描述
    private String desc;

    public User(String name,int age,String email,String desc){
        this.name = name;
        this.age = age;
        this.email = email;
        this.desc = desc;
    }
    /*
     * 重写 clone 方法,需要将权限改成 public ,直接调用父类的 clone 方法就好了
     */
    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
    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 String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getDesc() {
        return desc;
    }

    public void setDesc(String desc) {
        this.desc = desc;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", email='" + email + '\'' +
                ", desc='" + desc + '\'' +
                '}';
    }
}

package main.java.cn.test;

/**
 * @author ningzhaosheng
 * @date 2023/6/11 10:03:02
 * @description
 */
public class Test {
    public static void main(String[] args) throws Exception {
        // 初始化一个对象
        User user= new User("张三",20,"123456@qq.com","我是张三");
        // 复制对象
        User user1 = (User) user.clone();
        // 改变 user1 的属性值
        user1.setName("我是张三的克隆对象");
        // 修改 user age 的值
        user1.setAge(22);
        System.out.println("user对象:"+user.toString());
        System.out.println();
        System.out.println("usere1对象:"+user1.toString());

    }
}

改造很简单只需要让 User 继承 Cloneable 接口,并且重写 clone 方法即可,clone 也非常简单只需要调用 object 的 clone 方法就好,唯一需要注意的地方就是 clone 方法需要用 public 来修饰。

输出结果:

user对象:User{name='张三', age=20, email='123456@qq.com', desc='我是张三'}

usere1对象:User{name='我是张三的克隆对象', age=22, email='123456@qq.com', desc='我是张三'}

结果解读:

看到这个结果,你是否有所质疑呢?说好的引用对象只是拷贝了地址,为啥修改了 user1对象的 name 属性值,user对象没有改变?这里就是一个非常重要的知识点了,原因在于:「String、Integer 等包装类都是不可变的对象,当需要修改不可变对象的值时,需要在内存中生成一个新的对象来存放新的值,然后将原来的引用指向新的地址,所以在这里我们修改了 user1对象的 name 属性值,user1对象的 name 字段指向了内存中新的 name 对象,但是我们并没有改变 user对象的 name 字段的指向,所以 user 对象的 name 还是指向内存中原来的 name 地址,也就没有变化」。

这种引用是一种特列,因为这些引用具有不可变性,并不具备通用性,所以我们就自定义一个类,来演示浅拷贝,我们定义一个 UserDesc 类用来存放user 对象中的 desc 字段,,然后在 user 对象中引用 UserDesc 类。

示例代码:

package main.java.cn.test;

/**
 * @author ningzhaosheng
 * @date 2023/6/11 10:13:00
 * @description
 */
public class UserDesc {

    // 描述
    private String desc;

    public String getDesc() {
        return desc;
    }

    public void setDesc(String desc) {
        this.desc = desc;
    }

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

package main.java.cn.test;

/**
 * @author ningzhaosheng
 * @date 2023/6/11 10:04:25
 * @description
 */
public class User implements Cloneable{

    // 姓名
    private String name;
    // 年龄
    private int age;
    // 邮件
    private String email;
    // 描述
    private UserDesc userDesc;

    public User(String name,int age,String email,String desc){
        this.name = name;
        this.age = age;
        this.email = email;
        this.userDesc = new UserDesc();
        this.userDesc.setDesc(desc);
    }
    /*
     * 重写 clone 方法,需要将权限改成 public ,直接调用父类的 clone 方法就好了
     */
    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
    public void setDesc(String desc) {
        this.userDesc.setDesc(desc);
    }
    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 String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public UserDesc getUserDesc() {
        return userDesc;
    }

    public void setUserDesc(UserDesc desc) {
        this.userDesc = desc;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", email='" + email + '\'' +
                ", userDesc='" + userDesc.toString() + '\'' +
                '}';
    }
}

package main.java.cn.test;

/**
 * @author ningzhaosheng
 * @date 2023/6/11 10:03:02
 * @description
 */
public class Test {
    public static void main(String[] args) throws Exception {
        // 初始化一个对象
        User user= new User("小宁",20,"123456@qq.com","小宁的博客");
        // 复制对象
        User user1 = (User) user.clone();
        // 改变 user1 的属性值
        user1.setName("我是小宁的克隆对象");
        // 修改 user age 的值
        user1.setAge(22);
        user1.setDesc("我已经关注了小宁的博客啦");
        System.out.println("user对象:"+user.toString());
        System.out.println();
        System.out.println("usere1对象:"+user1.toString());

    }
}

运行 main 方法,得到如下结果:

user对象:User{name='小宁', age=20, email='123456@qq.com', userDesc='UserDesc{desc='我已经关注了小宁的博客啦'}'}

usere1对象:User{name='我是小宁的克隆对象', age=22, email='123456@qq.com', userDesc='UserDesc{desc='我已经关注了小宁的博客啦'}'}

结果解读:

我们修改 user1 的 desc 字段之后,user 的 desc 也发生了改变,这说明 user 对象和 user1 对象指向是同一个 UserDesc 对象地址,这也符合浅拷贝引用对象只拷贝引用地址并未创建新对象的定义,到这你应该知道浅拷贝了吧。

深拷贝

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

原理示意图:


深拷贝有两种方式,一种是跟浅拷贝一样实现 Cloneable 接口另一种是实现 Serializable 接口,用序列化的方式来实现深拷贝,我们分别用这两种方式来实现深拷贝。

 实现Cloneable接口方式

实现 Cloneable 接口的方式跟浅拷贝相差不大,我们需要引用对象也实现 Cloneable 接口。

具体代码改造如下:

package main.java.cn.test.clone.deep;

/**
 * @author ningzhaosheng
 * @date 2023/6/11 10:13:00
 * @description
 */
public class UserDesc implements Cloneable{

    // 描述
    private String desc;

    public String getDesc() {
        return desc;
    }

    public void setDesc(String desc) {
        this.desc = desc;
    }

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

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

package main.java.cn.test.clone.deep;


/**
 * @author ningzhaosheng
 * @date 2023/6/11 10:04:25
 * @description
 */
public class User implements Cloneable{

    // 姓名
    private String name;
    // 年龄
    private int age;
    // 邮件
    private String email;
    // 描述
    private UserDesc userDesc;

    public User(String name, int age, String email, String desc){
        this.name = name;
        this.age = age;
        this.email = email;
        this.userDesc = new UserDesc();
        this.userDesc.setDesc(desc);
    }
    /*
     * 重写 clone 方法,需要将权限改成 public ,直接调用父类的 clone 方法就好了
     */
    @Override
    public Object clone() throws CloneNotSupportedException {
        User user = (User) super.clone();
        // 需要将引用对象也克隆一次
        user.userDesc = (UserDesc)userDesc.clone();
        return user;
    }
    public void setDesc(String desc) {
        this.userDesc.setDesc(desc);
    }
    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 String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public UserDesc getUserDesc() {
        return userDesc;
    }

    public void setUserDesc(UserDesc desc) {
        this.userDesc = desc;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", email='" + email + '\'' +
                ", userDesc='" + userDesc.toString() + '\'' +
                '}';
    }
}

package main.java.cn.test.clone.deep;


/**
 * @author ningzhaosheng
 * @date 2023/6/11 10:03:02
 * @description
 */
public class Test {
    public static void main(String[] args) throws Exception {
        // 初始化一个对象
        User user= new User("小宁",20,"123456@qq.com","小宁的博客");
        // 复制对象
        User user1 = (User) user.clone();
        // 改变 user1 的属性值
        user1.setName("我是小宁的克隆对象");
        // 修改 user age 的值
        user1.setAge(22);
        user1.setDesc("我已经关注了小宁的博客啦");
        System.out.println("user对象:"+user.toString());
        System.out.println();
        System.out.println("usere1对象:"+user1.toString());

    }
}

我们再次运行 main 方法,得到如下结果:


user对象:User{name='小宁', age=20, email='123456@qq.com', userDesc='UserDesc{desc='小宁的博客'}'}

usere1对象:User{name='我是小宁的克隆对象', age=22, email='123456@qq.com', userDesc='UserDesc{desc='我已经关注了小宁的博客啦'}'}

结果解读:

可以看出,修改 user1 的 desc 时对 user 的 desc 已经没有影响了,说明进行了深拷贝,在内存中重新生成了一个新的对象。

实现 Serializable 接口方式

实现 Serializable 接口方式也可以实现深拷贝,而且这种方式还可以解决多层克隆的问题多层克隆就是引用类型里面又有引用类型,层层嵌套下去,用 Cloneable 方式实现还是比较麻烦的,一不小心写错了就不能实现深拷贝了,使用 Serializable 序列化的方式就需要所有的对象对实现 Serializable 接口,我们对代码进行改造,改造成序列化的方式

package main.java.cn.test.clone.deep;

import java.io.*;

/**
 * @author ningzhaosheng
 * @date 2023/6/11 11:00:58
 * @description
 */
public class UserDescBySerializable  implements Serializable {
    private static final long serialVersionUID = 872390113109L;
    // 描述
    private String desc;

    public String getDesc() {
        return desc;
    }

    public void setDesc(String desc) {
        this.desc = desc;
    }

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

package main.java.cn.test.clone.deep;

import java.io.*;

/**
 * @author ningzhaosheng
 * @date 2023/6/11 11:04:29
 * @description
 */
public class UserBySerializable implements Serializable {
    private static final long serialVersionUID = 369285298572941L;
    // 姓名
    private String name;
    // 年龄
    private int age;
    // 邮件
    private String email;
    // 描述
    private UserDescBySerializable userDesc;

    public UserBySerializable(String name, int age, String email, String desc){
        this.name = name;
        this.age = age;
        this.email = email;
        this.userDesc = new UserDescBySerializable();
        this.userDesc.setDesc(desc);
    }

    public UserBySerializable clone()  {
        UserBySerializable user  = null;
        try {
            // 将该对象序列化成流,因为写在流里的是对象的一个拷贝,而原对象仍然存在于JVM里面。所以利用这个特性可以实现对象的深拷贝
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(baos);
            oos.writeObject(this);
            // 将流序列化成对象
            ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bais);
            user = (UserBySerializable) ois.readObject();

        }catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return user;
    }
    public void setDesc(String desc) {
        this.userDesc.setDesc(desc);
    }
    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 String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public UserDescBySerializable getUserDesc() {
        return userDesc;
    }

    public void setUserDesc(UserDescBySerializable desc) {
        this.userDesc = desc;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", email='" + email + '\'' +
                ", userDesc='" + userDesc.toString() + '\'' +
                '}';
    }
}

package main.java.cn.test.clone.deep;


/**
 * @author ningzhaosheng
 * @date 2023/6/11 10:03:02
 * @description
 */
public class Test {
    public static void main(String[] args) throws Exception {
        // 初始化一个对象
        //User user= new User("小宁",20,"123456@qq.com","小宁的博客");
        UserBySerializable user = new UserBySerializable("小宁",20,"123456@qq.com","小宁的博客");
        // 复制对象
        //User user1 = (User) user.clone();
        UserBySerializable user1 = user.clone();
        // 改变 user1 的属性值
        user1.setName("我是小宁的克隆对象");
        // 修改 user age 的值
        user1.setAge(22);
        user1.setDesc("我已经关注了小宁的博客啦");
        System.out.println("user对象:"+user.toString());
        System.out.println();
        System.out.println("usere1对象:"+user1.toString());

    }
}

运行结果:


user对象:User{name='小宁', age=20, email='123456@qq.com', userDesc='UserDesc{desc='小宁的博客'}'}

usere1对象:User{name='我是小宁的克隆对象', age=22, email='123456@qq.com', userDesc='UserDesc{desc='我已经关注了小宁的博客啦'}'}

我们可以得到跟 Cloneable 方式一样的结果,序列化的方式也实现了深拷贝。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值