读《Effective Java》笔记 - 条目13

条目13:谨慎重写clone方法

浅拷贝和深拷贝

浅拷贝(Shallow Copy)

浅拷贝 只复制对象本身,而不复制对象引用的成员。 对于引用类型的字段,浅拷贝会将原对象的引用复制到新对象中,而不会创建新对象实例。因此,原对象和拷贝对象中的引用字段将指向相同的内存地址。

浅拷贝的特点:

  • 复制对象的时候,如果是基本数据类型会被完全复制。
  • 对于引用数据类型,比如数组,集合,自定义对象等,都是复制引用而不是实际的数据对象。
  • 浅拷贝通常是通过Object.clone()方法实现的。

示例:

java 复制代码
class Person {
    String name;
    int[] age;

    public Person(String name, int[] age) {
        this.name = name;
        this.age = age;
    }

    // 浅拷贝
    public Person shallowCopy() {
        try {
            Person cloned = (Person) super.clone();  // 复制对象
            return cloned;
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
            return null;
        }
    }
}

public class Main {
    public static void main(String[] args) {
        int[] ages = {25, 30, 35};
        Person person1 = new Person("John", ages);

        // 浅拷贝
        Person person2 = person1.shallowCopy();

        // 修改 person2 的 age 数组
        person2.age[0] = 40;

        System.out.println("person1's age: " + person1.age[0]);  // 40
        System.out.println("person2's age: " + person2.age[0]);  // 40
    }
}

person1person2age 数组是共享的,因为浅拷贝仅复制了 age 数组的引用。当 person2 修改了 age[0] 的值时,person1age[0] 也发生了变化。

深拷贝

深拷贝是指不仅复制对象本身,还递归地复制对象所引用的所有对象

深拷贝的特点:

  • 深拷贝会复制对象及其所有引用对象。
  • 每个引用类型字段都会被复制为一个全新的实例,因此原对象和拷贝对象中的引用字段指向不同的内存地址。
  • 深拷贝通常需要手动实现,尤其是在对象中包含其他引用类型。

示例:

java 复制代码
class Person implements Cloneable {
    String name;
    int[] age;

    public Person(String name, int[] age) {
        this.name = name;
        this.age = age;
    }

    // 深拷贝
    @Override
    public Person clone() {
        try {
            Person cloned = (Person) super.clone();  // 复制对象
            cloned.age = this.age.clone();  // 深拷贝数组
            return cloned;
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
            return null;
        }
    }
}

public class Main {
    public static void main(String[] args) {
        int[] ages = {25, 30, 35};
        Person person1 = new Person("John", ages);

        // 深拷贝
        Person person2 = person1.clone();

        // 修改 person2 的 age 数组
        person2.age[0] = 40;

        System.out.println("person1's age: " + person1.age[0]);  // 25
        System.out.println("person2's age: " + person2.age[0]);  // 40
    }
}

person1person2age 数组是完全独立的,因为我们在 clone() 方法中对 age 数组进行了深拷贝。修改 person2age[0] 不会影响 person1age[0]

Coneable接口

clone() 方法是 Object 类的一部分,因此所有的 Java 类都可以通过实现 Cloneable 接口来使得自己支持克隆。然而,问题在于 Cloneable 接口本身并没有提供任何方法,它只是一个标志,表示该类允许被克隆。要想正确的实现克隆,需要我们需要在类中覆盖 clone() 方法。如果直接使用弗雷的clone()方法,可能会在某些情况下得到不符合预期的结果。

建议

  1. 考虑使用构造函数代替clone()方法
  2. 如果决定重写clone()方法,需要调用super.clone()来确保父类对象的字段也会被复制,如果没有,可能会导致父类无法正确的被克隆。
  3. 如果要处理字段是引用类型(对象)的情况时,需要对这些字段创建新的实例,从而确保是深拷贝。
  4. clone() 方法必须声明为 public,因为它是从 Object 类继承来的,默认是 protected,因此在覆盖时需要改变它的访问修饰符。
  5. clone() 方法必须抛出 CloneNotSupportedException 异常,这是因为 Cloneable 接口并不是强制要求实现的,如果一个类没有实现 Cloneable 接口而调用 clone(),将会抛出这个异常。
  6. 要么完全重写,要么不重写。如果重写的话,就需要全面考虑所有的字段。特别是当对象包含复杂的嵌套结构时,确保每一个引用字段都能正确的被复制。
  7. 复制最好通过构造器或者工厂来提供。
相关推荐
零基础的修炼1 分钟前
Linux---序列化与反序列化
java·开发语言
敲代码的嘎仔7 分钟前
JavaWeb零基础学习Day6——JDBC
java·开发语言·sql·学习·spring·单元测试·maven
闭着眼睛学算法9 分钟前
【双机位A卷】华为OD笔试之【排序】双机位A-银行插队【Py/Java/C++/C/JS/Go六种语言】【欧弟算法】全网注释最详细分类最全的华子OD真题题解
java·c语言·javascript·c++·python·算法·华为od
TL滕10 分钟前
从0开始学算法——第一天(认识算法)
数据结构·笔记·学习·算法
YuforiaCode17 分钟前
神领物流v2.0-day3-运费微服务笔记(个人记录、含练习答案、仅供参考)
笔记
GIS瞧葩菜18 分钟前
【无标题】
开发语言·前端·javascript·cesium
熙客19 分钟前
RabbitMQ:灵活路由和高可靠的消息队列
java·中间件·rabbitmq·java-rabbitmq
论迹1 小时前
【JavaEE】-- Cookie &&Session
java·java-ee
zhangrelay1 小时前
如何使用AI快速编程实现标注ROS2中sensor_msgs/msg/Image图像色彩webots2025a
人工智能·笔记·opencv·学习·计算机视觉·机器人视觉
j_xxx404_1 小时前
C++ STL:list|了解list|相关接口|相关操作
开发语言·c++