Java中的复制

概述

  • 将一个对象的引用复制给另外一个对象。

  • 有三种方式:直接赋值、浅拷贝、深拷贝。

  • 这三种概念实际上都是为了拷贝对象。

直接赋值复制

  • 直接赋值复制指的是在Java中,使用"="符号进行赋值时,实际复制的是引用而不是值本身

  • 比如,表达式"a1=a2"中赋值给a1的是一个引用地址,即a1和a2都指向同一个对象。

  • 因此,如果a1发生变化,与之关联的a2对象中的成员变量也会跟着变化。

深拷贝与浅拷贝

img

浅拷贝

  • 浅拷贝是指在对一个对象进行拷贝时,只拷贝对象本身和其中的基本数据类型,而不拷贝对象内部的引用类型

  • 在Java中,实现浅拷贝可以通过实现Cloneable接口。

  • 例如以下代码:Person类包含一个引用类型属性address。当对Person实例original执行浅拷贝并将结果赋给copied后,修改copied中的address属性值时,会发现original中的address属性值也被同步修改。这是因为浅拷贝仅复制了原始对象的引用类型成员变量到新对象中,导致原始对象和新对象的该属性都指向同一内存地址。因此,当原始对象的引用类型成员变量发生改变时,新对象的对应属性也会随之变化

  • class Address {
        String city;
        String street;
    
        Address(String city, String street) {
            this.city = city;
            this.street = street;
        }
    }
    
    class Person implements Cloneable {
        String name;
        int age;
        Address address;  // 引用类型
    
        Person(String name, int age, Address address) {
            this.name = name;
            this.age = age;
            this.address = address;
        }
    
        @Override
        protected Object clone() throws CloneNotSupportedException {
            return super.clone();  // 使用Object的默认clone方法实现浅拷贝
        }
    }
    
    public class ShallowCopyExample {
        public static void main(String[] args) throws CloneNotSupportedException {
            Address addr = new Address("New York", "5th Avenue");
            Person original = new Person("John", 30, addr);
    
            // 执行浅拷贝
            Person copied = (Person) original.clone();
    
            // 修改拷贝后的引用类型字段
            copied.address.city = "Los Angeles";
    
            // 原始对象的address也被修改了
            System.out.println(original.address.city);  // 输出: Los Angeles
        }
    }
     
    

深拷贝

  • 在拷贝对象时,不仅复制对象本身及其基本数据类型,还会递归复制对象内部的所有引用类型数据。

  • 即克隆后的对象中,其引用类型的变量指向的是全新的地址,而不是被克隆对象中该属性的地址。

  • 因此,因此,修改新对象中的引用类型属性时,不会对原对象产生任何影响。

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

构造函数实现深拷贝

  • 我们可以调用构造函数进行深拷贝

  • 形参如果是基本类型和字符串则是直接赋值,如果是对象,则是重新new一个。

public class Address {
    String city;
    String street;
    //构造函数、setter/getter省略
}
public class UserConstruct {
​
    private String userName;
​
    private Address address;//引用类型的属性
​
    //构造函数、setter/getter省略
​
    public static void main(String[] args) {
        AddressConstruct address = new AddressConstruct("city", "street");
        UserConstruct user = new UserConstruct("小李", address);
        // 调用构造函数进行深拷贝
        UserConstruct copyUser = new UserConstruct(user.getUserName(), new AddressConstruct(address.getAddress1(), address.getAddress2()));
        // 修改源对象中adress属性的值
        user.getAdress().getCity().setCity("city2");
        // 对比user和copyUser,输出false,因为user中adress属性属性变了,
        System.out.println(user == copyUser);
        // 对比两个对象中的address属性,同样输出false,因为由于是深拷贝,copyUser中该属性不会受到影响
        System.out.println(user.getAddress() == copyUser.getAddress());
​
    }
}
​

重写clone方法实现深拷贝

public class AddressClone implements Cloneable{
    private String address1;
    private String address2;
    

    //构造方法、setter/getter省略
    
    /**
     * 重写clone方法实现浅拷贝
     * @return 克隆后的AddressClone对象
     * @throws CloneNotSupportedException 如果对象不支持克隆则抛出异常
     */
    @Override
    protected AddressClone clone() throws CloneNotSupportedException {
        // 调用Object类的native clone方法进行浅拷贝
        return (AddressClone) super.clone();
    }
}
/**
 * 用户类,通过实现Cloneable接口实现深拷贝
 */
public class UserClone implements Cloneable{
    private String userName;
    private AddressClone address;
    
    //构造方法、setter/getter省略
    
    /**
     * 重写clone方法实现深拷贝
     * 子类还需要实现Cloneable接口来告诉JVM这个类是可以拷贝的。
     * @return 深拷贝后的UserClone对象
     * @throws CloneNotSupportedException 如果对象不支持克隆则抛出异常
     */
    @Override
    protected UserClone clone() throws CloneNotSupportedException {
        // 1. 先调用父类的clone方法完成基本类型的浅拷贝
        UserClone userClone = (UserClone) super.clone();
        
        // 2. 对引用类型address单独进行克隆,实现深拷贝
        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
        
        // 测试两个用户对象的address1是否相同(应该是false,因为进行了深拷贝)
        System.out.println(user.getAddress().getAddress1().equals(copyUser.getAddress().getAddress1()));  // false
    }
}

序列化方式实现深拷贝

实现Serializable接口
  • Java提供了序列化的能力,我们可以先将源对象进行序列化,再反序列化生成拷贝对象

  • 但是,使用序列化的前提是拷贝的类(包括其成员变量)需要实现Serializable接口。

  • // Person类,实现Serializable接口,表示一个人的信息
    class Person implements Serializable {
        private String name; // 姓名
        private Address address; // 地址
    ​
        //构造方法、setter/getter省略
    }
    ​
    // Address类,表示一个地址信息
    class Address implements Serializable {
        private String city; // 城市
    ​
        // 构造方法、setter/getter省略
    }
    ​
    public class Test {
        ​
        // 深拷贝方法,将传入的对象序列化到字节数组中,再从字节数组反序列化为新的对象
        private static Object deepCopy(Object obj) throws Exception {
            // 创建一个字节数组输出流对象,用于存储序列化后的字节数组
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
    
    
            // 创建一个对象输出流对象,用于将对象序列化到字节数组输出流中
            ObjectOutputStream oos = new ObjectOutputStream(bos);
    
    
            // 将传入的对象写入对象输出流中,实现对象的序列化
            oos.writeObject(obj);
    
    
            // 创建一个字节数组输入流对象,用于读取序列化后的字节数组
            ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
    
    
            // 创建一个对象输入流对象,用于将字节数组输入流中的数据反序列化为对象
            ObjectInputStream ois = new ObjectInputStream(bis);
    
    
            // 从对象输入流中读取对象,实现对象的反序列化,并返回反序列化后的新对象
            return ois.readObject();
    
        }
    
        public static void main(String[] args) throws Exception {
            // 创建一个Address对象,表示北京市的地址
            Address address = new Address("Beijing");
    
    
            // 创建一个Person对象,名为Tom,拥有一个Address对象作为属性
            Person person1 = new Person("Tom", address);
    
    
            // 使用深拷贝方法创建一个新的Person对象,与person1对象无关
            Person person2 = (Person) deepCopy(person1);
    
            // 修改person2中的address属性
            person2.setAddress ("shanghai");
    
    
            // 输出person1和person2是否相等(false),因为它们是不同的对象
            System.out.println(person1 == person2);
    
    
            // 输出person1和person2的Address对象是否相等(false),因为它们是不同的对象
            System.out.println(person1.getAddress() == person2.getAddress());
    
    
        }
    ​
    }
Gson序列化方式深拷贝
public class AddressGson {
​
    private String address1;
​
    private String address2;
​
    //构造函数、setter/getter省略
}
​
​
/**
 * Gson可以将对象序列化成JSON,也可以将JSON反序列化成对象,所以我们可以用它进行深拷贝
 */
public class UserGson {
    private String userName;
​
    private AddressGson address;
​
    //构造函数、setter/getter省略
​
    public static void main(String[] args) {

        AddressGson address = new AddressGson("小区1", "小区2");
        UserGson user = new UserGson("小李", address);


        // 使用Gson序列化进行深拷贝:通过将源对象序列化成json,然后再反序列化
        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()));
    }
​
}
Jackson序列化方式
public class AddressJackson {
    private String address1;
​
    private String address2;
​
    //构造函数、setter/getter省略
}
​

​
/**
 * 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()));
    }
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值