java深拷贝的几种方式

 2022-09-16
原文地址:https://blog.csdn.net/qq_40093255/article/details/115691423

前言

在java里,当我们需要拷贝一个对象时,有两种类型的拷贝:浅拷贝与深拷贝。浅拷贝只是拷贝了源对象的地址,所以源对象的值发生变化时,拷贝对象的值也会发生变化。而深拷贝则是拷贝了源对象的所有值,所以即使源对象的值发生变化时,拷贝对象的值也不会改变。

202209162057096981.png

方式1:构造函数深拷贝

我们可以调用构造函数进行深拷贝,形参如果是基本类型和字符串则是直接赋值,如果是对象,则是重新new一个。

测试案例

    package com.lyj.demo.pojo.cloneTest;
    
    import lombok.Getter;
    
    /**
     * @author 凌兮
     * @date 2021/4/15 14:28
     * 通过构造器进行深拷贝测试
     */
    @Getter
    public class UserConstruct {
    
        private String userName;
    
        private AddressConstruct address;
    
        public UserConstruct() {
        }
    
        public UserConstruct(String userName, AddressConstruct address) {
            this.userName = userName;
            this.address = address;
        }
    
        public static void main(String[] args) {
            AddressConstruct address = new AddressConstruct("小区1", "小区2");
            UserConstruct user = new UserConstruct("小李", address);
            // 调用构造函数进行深拷贝
            UserConstruct copyUser = new UserConstruct(user.getUserName(), new AddressConstruct(address.getAddress1(), address.getAddress2()));
            // 修改源对象的值
            user.getAddress().setAddress1("小区3");
            // false
            System.out.println(user == copyUser);
            // false
            System.out.println(user.getAddress().getAddress1() == copyUser.getAddress().getAddress1());
            // false
            System.out.println(user.getAddress().getAddress1().equals(copyUser.getAddress().getAddress1()));
            // true
            System.out.println(user.getAddress().getAddress2().equals(copyUser.getAddress().getAddress2()));
    
        }
    }
    
    package com.lyj.demo.pojo.cloneTest;
    
    import lombok.Getter;
    import lombok.Setter;
    
    /**
     * @author 凌兮
     * @date 2021/4/15 14:28
     */
    @Getter
    @Setter
    public class AddressConstruct {
    
        private String address1;
    
        private String address2;
    
        public AddressConstruct() {
        }
    
        public AddressConstruct(String address1, String address2) {
            this.address1 = address1;
            this.address2 = address2;
        }
    }

方式2:重载Clone()方法深拷贝

Object父类有个clone()的拷贝方法,不过它是protected类型的 ,我们需要重写它并修改为public类型,除此之外,子类还需要实现Cloneable接口来告诉JVM这个类上市可以拷贝的。

测试案例

    package com.lyj.demo.pojo.cloneTest;
    
    import lombok.Getter;
    import lombok.Setter;
    
    /**
     * @author 凌兮
     * @date 2021/4/15 14:49
     *
     */
    @Setter
    @Getter
    public class AddressClone implements Cloneable{
    
        private String address1;
    
        private String address2;
    
        public AddressClone() {
        }
    
        public AddressClone(String address1, String address2) {
            this.address1 = address1;
            this.address2 = address2;
        }
    
        @Override
        protected AddressClone clone() throws CloneNotSupportedException {
            return (AddressClone) super.clone();
        }
    }
    
    package com.lyj.demo.pojo.cloneTest;
    
    import lombok.Getter;
    import lombok.Setter;
    
    /**
     * @author 凌兮
     * @date 2021/4/15 14:48
     * 通过实现Clone接口实现深拷贝
     */
    @Setter
    @Getter
    public class UserClone implements Cloneable{
        private String userName;
    
        private AddressClone address;
    
        public UserClone() {
        }
    
        public UserClone(String userName, AddressClone address) {
            this.userName = userName;
            this.address = address;
        }
    
        /**
         * Object父类有个clone()的拷贝方法,不过它是protected类型的,
         * 我们需要重写它并修改为public类型。除此之外,
         * 子类还需要实现Cloneable接口来告诉JVM这个类是可以拷贝的。
         * @return
         * @throws CloneNotSupportedException
         */
        @Override
        protected UserClone clone() throws CloneNotSupportedException {
            // 需要注意的是,super.clone()其实是浅拷贝,
            // 所以在重写UserClone类的clone()方法时,address对象需要调用address.clone()重新赋值
            UserClone userClone = (UserClone) super.clone();
            userClone.setAddress(this.address.clone());
            return userClone;
        }
    
        public static void main(String[] args) throws CloneNotSupportedException {
            AddressClone address = new AddressClone("小区1", "小区2");
            UserClone user = new UserClone("小李", address);
            UserClone copyUser = user.clone();
            user.getAddress().setAddress1("小区3");
            // false
            System.out.println(user == copyUser);
            // false
            System.out.println(user.getAddress().getAddress1().equals(copyUser.getAddress().getAddress1()));
        }
    }

需要注意的是,super.clone()其实是浅拷贝,所以在重写User类的clone()方法时,address对象需要调用address.clone()重新赋值。

方式3:Apache Commons Lang序列化方式深拷贝

Java提供了序列化的能力,我们可以先将源对象进行序列化,再反序列化生成拷贝对象。但是,使用序列化的前提是拷贝的类(包括其成员变量)需要实现Serializable接口。Apache Commons Lang包对Java序列化进行了封装,我们可以直接使用它。

测试案例

    package com.lyj.demo.pojo.cloneTest;
    
    import lombok.Getter;
    import lombok.Setter;
    
    import java.io.Serializable;
    
    /**
     * @author 凌兮
     * @date 2021/4/15 15:11
     */
    @Getter
    @Setter
    public class AddressSerializable implements Serializable {
    
        private String address1;
    
        private String address2;
    
        public AddressSerializable() {
        }
    
        public AddressSerializable(String address1, String address2) {
            this.address1 = address1;
            this.address2 = address2;
        }
    }
    package com.lyj.demo.pojo.cloneTest;
    
    import lombok.Getter;
    import lombok.Setter;
    import org.apache.commons.lang3.SerializationUtils;
    
    import java.io.Serializable;
    
    /**
     * @author 凌兮
     * @date 2021/4/15 15:10
     * 通过Apache Commons Lang 序列化方式深拷贝
     * Java提供了序列化的能力,我们可以先将源对象进行序列化,再反序列化生成拷贝对象。
     * 但是,使用序列化的前提是拷贝的类(包括其成员变量)需要实现Serializable接口。
     * Apache Commons Lang包对Java序列化进行了封装,我们可以直接使用它。
     */
    @Getter
    @Setter
    public class UserSerializable implements Serializable {
    
        private String userName;
    
        private AddressSerializable address;
    
        public UserSerializable() {
        }
    
        public UserSerializable(String userName, AddressSerializable address) {
            this.userName = userName;
            this.address = address;
        }
    
        public static void main(String[] args) {
            AddressSerializable address = new AddressSerializable("小区1", "小区2");
            UserSerializable user = new UserSerializable("小李", address);
            UserSerializable copyUser = SerializationUtils.clone(user);
            user.getAddress().setAddress1("小区3");
            // false
            System.out.println(user == copyUser);
            // false
            System.out.println(user.getAddress().getAddress1().equals(copyUser.getAddress().getAddress1()));
    
        }
    }

方式4:Gson序列化方式深拷贝

Gson可以将对象序列化成JSON,也可以将JSON反序列化成对象,所以我们可以用它进行深拷贝。

测试案例

    package com.lyj.demo.pojo.cloneTest;
    
    import lombok.Data;
    
    /**
     * @author 凌兮
     * @date 2021/4/15 15:31
     */
    @Data
    public class AddressGson {
    
        private String address1;
    
        private String address2;
    
        public AddressGson() {
        }
    
        public AddressGson(String address1, String address2) {
            this.address1 = address1;
            this.address2 = address2;
        }
    }
    
    package com.lyj.demo.pojo.cloneTest;
    
    import com.google.gson.Gson;
    import lombok.Data;
    
    /**
     * @author 凌兮
     * @date 2021/4/15 15:30
     * 使用Gson序列化方式进行深拷贝
     * Gson可以将对象序列化成JSON,也可以将JSON反序列化成对象,所以我们可以用它进行深拷贝
     */
    @Data
    public class UserGson {
        private String userName;
    
        private AddressGson address;
    
        public UserGson() {
        }
    
        public UserGson(String userName, AddressGson address) {
            this.userName = userName;
            this.address = address;
        }
    
        public static void main(String[] args) {
            AddressGson address = new AddressGson("小区1", "小区2");
            UserGson user = new UserGson("小李", address);
            // 使用Gson序列化进行深拷贝
            Gson gson = new Gson();
            UserGson copyUser = gson.fromJson(gson.toJson(user), UserGson.class);
            user.getAddress().setAddress1("小区3");
            // false
            System.out.println(user == copyUser);
            // false
            System.out.println(user.getAddress().getAddress1().equals(copyUser.getAddress().getAddress1()));
        }
    
    }

方式5:Jackson序列化方式

Jackson与Gson相似,可以将对象序列化成JSON,明显不同的地方是拷贝的类(包括其成员变量)需要有默认的无参构造函数。

测试案例

    package com.lyj.demo.pojo.cloneTest;
    
    import lombok.Data;
    
    /**
     * @author 凌兮
     * @date 2021/4/15 15:41
     */
    @Data
    public class AddressJackson {
        private String address1;
    
        private String address2;
    
        public AddressJackson() {
        }
    
        public AddressJackson(String address1, String address2) {
            this.address1 = address1;
            this.address2 = address2;
        }
    }
    
    package com.lyj.demo.pojo.cloneTest;
    
    import com.fasterxml.jackson.core.JsonProcessingException;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import lombok.Data;
    
    /**
     * @author 凌兮
     * @date 2021/4/15 15:40
     * 通过Jackson方式实现深拷贝
     * Jackson与Gson相似,可以将对象序列化成JSON,明显不同的地方是拷贝的类(包括其成员变量)需要有默认的无参构造函数。
     */
    @Data
    public class UserJackson {
        private String userName;
    
        private AddressJackson address;
    
        public UserJackson() {
        }
    
        public UserJackson(String userName, AddressJackson address) {
            this.userName = userName;
            this.address = address;
        }
    
        public static void main(String[] args) throws JsonProcessingException {
            AddressJackson address = new AddressJackson("小区1", "小区2");
            UserJackson user = new UserJackson("小李", address);
            // 使用Jackson序列化进行深拷贝
            ObjectMapper objectMapper = new ObjectMapper();
            UserJackson copyUser = objectMapper.readValue(objectMapper.writeValueAsString(user), UserJackson.class);
            user.getAddress().setAddress1("小区3");
            // false
            System.out.println(user == copyUser);
            // false
            System.out.println(user.getAddress().getAddress1().equals(copyUser.getAddress().getAddress1()));
        }
    }

总结

`

深拷贝方法 优点 缺点
构造函数 1.底层实现简单2.不需要引入第三方包3.系统开销小4.对拷贝类没有要求,不需要实现额外接口和方法 1.可用性差,每次新增成员变量都需要新增新的拷贝构造函数
重载clone()方法 1.底层实现较简单2.不需要引入第三方包3.系统开销小 1.可用性较差,每次新增成员变量可能需要修改clone()方法2.拷贝类(包括其成员变量)需要实现Cloneable接口
ApacheCommonsLang序列化 1.可用性强,新增成员变量不需要修改拷贝方法 1.底层实现较复杂2.需要引入ApacheCommonsLang第三方JAR包3.拷贝类(包括其成员变量)需要实现Serializable接口4.序列化与反序列化存在一定的系统开销
Gson序列化 1.可用性强,新增成员变量不需要修改拷贝方法2.对拷贝类没有要求,不需要实现额外接口和方法 1.底层实现复杂2.需要引入Gson第三方JAR包3.序列化与反序列化存在一定的系统开销
Jackson序列化 1.可用性强,新增成员变量不需要修改拷贝方法 1.底层实现复杂2.需要引入Jackson第三方JAR包3.拷贝类(包括其成员变量)需要实现默认的无参构造函数4.序列化与反序列化存在一定的系统开销