深入理解Java浅拷贝与深拷贝

Java中的对象拷贝是一个重要的概念,特别是在面向对象编程中。Java提供了两种类型的拷贝方式:浅拷贝和深拷贝。

浅拷贝只是复制引用,而不是创建一个新的对象,所以新的对象和原始对象引用同样的数据。而深拷贝是创建一个新的对象,与原始对象完全独立,拥有自己的内存空间和值。

本文将会深入探讨Java浅拷贝和深拷贝的概念,讲解它们各自的用途、使用场景以及技巧,并通过实战案例帮助读者更好地理解这两种拷贝方式的含义。

浅拷贝

什么是浅拷贝?

Java浅拷贝是指只复制对象的引用,而不是创建一个新的对象。这意味着,修改新对象将会影响原始对象。浅拷贝通常是通过Object类的clone()方法来实现的,它只对八种基本数据类型和对象引用类型生效,对于其他数据类型均无法实现浅拷贝。

如何实现浅拷贝?

Java中的Object类提供了一个克隆方法clone(),如果想要进行浅拷贝,我们只需要实现Cloneable接口,并重写clone()方法即可。下面是一个简单的例子:

public class Employee implements Cloneable {
    private String name;
    private int age;
    private Address address;

    public Employee(String name, int age, Address address) {
        this.name = name;
        this.age = age;
        this.address = address;
    }

    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 Address getAddress() {
        return address;
    }
    public void setAddress(Address address) {
        this.address = address;
    }

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

public class Address {
    private String street;
    private String city;

    public Address(String street, String city) {
        this.street = street;
        this.city = city;
    }

    public String getStreet() {
        return street;
    }
    public void setStreet(String street) {
        this.street = street;
    }
    public String getCity() {
        return city;
    }
    public void setCity(String city) {
        this.city = city;
    }
}

// 测试代码
Employee emp1 = new Employee("Tom", 28, new Address("1234", "New York"));
Employee emp2 = (Employee)emp1.clone(); // 浅拷贝
System.out.println(emp1 == emp2); // false,引用不同
System.out.println(emp1.getAddress() == emp2.getAddress()); // true,地址相同

从上述代码可以看出,我们通过实现Cloneable接口并重写clone()方法来实现了浅拷贝。然后我们创建了一个Employee对象emp1,并赋予了相应的属性值。接下来,我们通过emp1.clone()进行浅拷贝,生成了另一个Employee对象emp2。可以看到,emp1和emp2的引用不同,但是它们共用了同一个address对象。

浅拷贝的缺点

浅拷贝相对于深拷贝,有一些明显的缺点:

1、受到原始对象的影响

由于浅拷贝仅仅复制了对象的引用,所以对浅拷贝对象的修改会对原始对象产生影响。这就导致当我们在修改浅拷贝对象时,可能会意外地修改了原始对象。

// 测试代码
Employee emp1 = new Employee("Tom", 28, new Address("1234", "New York"));
Employee emp2 = (Employee)emp1.clone(); // 浅拷贝
emp2.getAddress().setStreet("5678");

System.out.println(emp1.getAddress().getStreet()); // 5678,原始对象被修改

从上述代码中可以看到,我们修改了emp2对象所引用Address对象的街道地址,然后再次访问emp1对象的Address对象,发现它已经改变了,这表明原始对象受到了浅拷贝对象的影响。

2、无法复制非基本类型的数据成员

浅拷贝只能复制基本类型的数据,对于复杂类型的数据结构,如数组、对象等就无法完成深度拷贝。因为浅拷贝只是简单地复制引用,而不是创建一个新的对象。

深拷贝

什么是深拷贝?

相对于浅拷贝,深拷贝会创建一个新的对象,并将原始对象的所有属性也复制到新创建的对象中。这意味着,修改新对象不会影响到原始对象。深拷贝通常使用Java序列化API实现,但也可以通过流式处理、ObjectInputStream/ObjectOutputStream来实现。

如何实现深拷贝?

Java中提供了几种实现深拷贝的方式,下面分别介绍:

1、序列化实现深拷贝

Java中提供了一种默认的序列化和反序列化机制,通过这种机制我们可以实现深拷贝。这种方法很简单,只需要在要拷贝的对象上实现Serializable接口即可。序列化拷贝的过程如下:

// 将对象序列化
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 class Employee implements Serializable {
    private String name;
    private int age;
    private Address address;

    public Employee(String name, int age, Address address) {
        this.name = name;
        this.age = age;
        this.address = address;
    }

    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 Address getAddress() {
        return address;
    }
    public void setAddress(Address address) {
        this.address = address;
    }
}

public class Address implements Serializable {
    private String street;
    private String city;

    public Address(String street, String city) {
        this.street = street;
        this.city = city;
    }

    public String getStreet() {
        return street;
    }
    public void setStreet(String street) {
        this.street = street;
    }
    public String getCity() {
        return city;
    }
    public void setCity(String city) {
        this.city = city;
    }
}

// 测试代码
Employee emp1 = new Employee("Tom", 28, new Address("1234", "New York"));
Employee emp2 = (Employee) deepCopy(emp1); // 序列化实现深拷贝
System.out.println(emp1 == emp2); // false,引用不同
System.out.println(emp1.getAddress() == emp2.getAddress()); // false,地址不同

private static Object deepCopy(Object obj) throws IOException, ClassNotFoundException {
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    ObjectOutputStream oos = new ObjectOutputStream(bos);
    oos.writeObject(obj);
    ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()));
    return ois.readObject();
}

从上述代码中可以看到,我们通过实现Serializable接口并序列化与反序列化来实现了深拷贝。然后我们创建了一个Employee对象emp1,并赋予了相应的属性值。接下来,我们通过deepCopy()方法进行深拷贝,生成了另一个Employee对象emp2。可以看到,emp1和emp2的引用不同,且它们的地址也不同,表明已经完成了深拷贝。

2、流式处理实现深拷贝

流处理也可以实现深拷贝。Java 8 加入的 Streams API 可以用于快速轻松地处理集合或其他数据源中的元素。Streams 的一个常见模式是将数据源中的所有元素收集到一个新的集合中。这个过程被称为转换,也可以用于深拷贝。

下面是一个具体的例子:

public class Employee implements Cloneable {
    private String name;
    private int age;
    private Address address;
    // 省略构造器、get/set方法等

    @Override
    protected Object clone() throws CloneNotSupportedException {
        Employee empClone = (Employee)super.clone();
        empClone.setAddress(new Address(address.getStreet(), address.getCity())); // 注意深拷贝
        return empClone;
    }
}

public class Address {
    private String street;
    private String city;
    // 省略构造器、get/set方法等
}

// 测试代码
Employee emp1 = new Employee("Tom", 28, new Address("1234", "New York"));
Employee emp2 = (Employee)emp1.clone(); // 流式处理实现深拷贝
System.out.println(emp1 == emp2); // false,引用不同
System.out.println(emp1.getAddress() == emp2.getAddress()); // false,地址不同

从上述代码中可以看到,我们通过重写clone()方法,并采用流式处理的方式实现了深拷贝。然后我们创建了一个Employee对象emp1,并赋予了相应的属性值。

接下来,我们通过emp1.clone()进行深拷贝,生成了另一个Employee对象emp2。可以看到,emp1和emp2的引用不同,且它们的地址也不同,表明已经完成了深拷贝。

深拷贝的优点

深拷贝与浅拷贝相比,具有以下优点:

1、对于复杂数据结构进行了完整的拷贝

深拷贝会对所有的属性值进行拷贝,因此对于复杂数据结构,如对象数组、嵌套对象、多层嵌套等一系列的数据结构都能够得到完整的拷贝。

2、完全独立

深拷贝不会受到原始对象的影响,因此可以完全独立于原始对象进行操作。

3、引用地址不同

深拷贝会创建一个新的对象,该对象的引用地址与原始对象不同。这意味着,深拷贝不会改变原始对象的状态,从而更加安全可靠。

浅拷贝和深拷贝的区别

浅拷贝只是复制引用,而不是创建一个新的对象,它们共用一份数据。

深拷贝会创建一个新的对象,该对象的所有属性值都会被完整拷贝,可以与原始对象完全独立进行操作。

浅拷贝与深拷贝的主要区别就是浅拷贝只关注对象内存地址,而深拷贝关注对象本身。

如何选择浅拷贝和深拷贝?

在实际应用中,我们应该根据具体的需求来选择使用浅拷贝还是深拷贝。有以下几点需要考虑:

对象的结构和复杂度

如果对象结构简单且属性值基本都是基本类型,则可以使用浅拷贝;反之,深拷贝更为安全。

对象的大小

如果拷贝的对象非常大,则使用浅拷贝可能会消耗大量的时间和内存资源,此时深拷贝可以减少对时间和内存的消耗。

操作的安全性

如果操作需要保证数据的安全性,则应该使用深拷贝。因为在使用浅拷贝时,被拷贝的对象可能会被意外修改。

扩展思考

在 Java 中,List 类型的浅拷贝和深拷贝可以通过什么方式实现?

浅拷贝

List 的浅拷贝可以通过调用 List 接口提供的 clone() 方法实现。该方法会返回一个与原始对象具有相同元素的新对象,但是新对象和原始对象共享相同元素的引用。

示例代码如下:

需要注意的是,在进行深拷贝时,被复制的对象必须实现 Cloneable 或 Serializable 接口,否则将会抛出异常。同时,在使用序列化方式进行深拷贝时,被复制的对象需要保证其所有成员变量都是可序列化的。

总的来说,使用深拷贝更为安全,但是也相对更加消耗时间和内存资源。因此在选择使用浅拷贝或深拷贝时,需要权衡需求和性能。

List<Object> originalList = new ArrayList<>();
// 假设 originalList 中已经存在一些元素
List<Object> shallowCopyList = (ArrayList<Object>) originalList.clone();

深拷贝

List 的深拷贝需要对集合中的每个元素进行递归复制,确保新对象中的所有元素都是新创建的对象,而不是原始对象的引用。这通常需要使用一个循环遍历集合,对每一个元素进行判断和复制。

示例代码如下:

List<Object> originalList = new ArrayList<>();
// 假设 originalList 中已经存在一些元素
List<Object> deepCopyList = new ArrayList<>();
for (Object element : originalList) {
    if (element instanceof Cloneable) { // 可以被克隆
        try {
            Method cloneMethod = element.getClass().getMethod("clone");
            Object copiedElement = cloneMethod.invoke(element);
            deepCopyList.add(copiedElement);
        } catch (Exception e) {
            // 处理异常
        }
    } else if (element instanceof Serializable) { // 可以被序列化
        try {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(baos);
            oos.writeObject(element);
            ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bais);
            Object copiedElement = ois.readObject();
            deepCopyList.add(copiedElement);
        } catch (Exception e) {
            // 处理异常
        }
    } else { // 不可复制
        deepCopyList.add(element);
    }
}

需要注意的是,在进行深拷贝时,被复制的对象必须实现 Cloneable 或 Serializable 接口,否则将会抛出异常。同时,在使用序列化方式进行深拷贝时,被复制的对象需要保证其所有成员变量都是可序列化的。

总的来说,使用深拷贝更为安全,但是也相对更加消耗时间和内存资源。因此在选择使用浅拷贝或深拷贝时,需要权衡需求和性能。

后:下方这份完整的软件测试视频教程已经整理上传完成,需要的朋友们可以自行领取【保证100%免费】

软件测试面试文档

我们学习必然是为了找到高薪的工作,下面这些面试题是来自阿里、腾讯、字节等一线互联网大厂最新的面试资料,并且有字节大佬给出了权威的解答,刷完这一套面试资料相信大家都能找到满意的工作。

猜你喜欢

转载自blog.csdn.net/wx17343624830/article/details/132859060
今日推荐