Java对象克隆——浅拷贝与深拷贝

本文详细介绍了Java中对象克隆的基本概念,包括浅拷贝与深拷贝的区别,并提供了具体的Employee类实例代码来演示如何实现这两种拷贝。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

       今天收到经理的一封Email,里面是关于对象拷贝的东西,实例代码是C#的,大概看了下,基本还是能看明白!下来觉得还是总结下Java中的对象克隆的知识点以便日后查阅。


对象克隆


      当我们拷贝一个变量时,原始变量与拷贝变量引用同一个对象,这就是说,改变一个变量所引用的对象将会对另一个变量产生影响。

Employee类:

/**
 * @author XzZhao
 */
public class Employee {
    private String name;
    private double salary;
    private Date   hireDay;

    public Employee(String name, double salary) {
        super();
        this.name = name;
        this.salary = salary;
    }

    public void raiseSalary(double byPercent) {
        double raise = salary * byPercent / 100;
        salary += raise;
    }

    /******* get set *********/
}
Test:
public static void main(String[] args) {
        Employee original = new Employee("John", 50000);
        Employee copy = original;
        copy.raiseSalary(10);
        System.out.println(original.getSalary()); // original changed
    }

        如果创建一个对象的新的copy,它的最初状态与original一样,但以后将可以各自改变各自的状态,那就需要clone方法。clone是Object类的一个proteced方法,在代码中不能直接调用它。只有Employee类才能够克隆Employee对象。如果对象中的所有数据域都属于数值或基本类型,这样拷贝域没有任何问题。但是,如果在对象中包含了子对象的引用,拷贝的结果会使得两个域引用同一个子对象,因此原始对象与克隆对象共享这部分信息。


浅拷贝


     Object类的clone方法克隆对象,默认的克隆操作是浅拷贝,它并没有克隆包含在对象中的内部对象。

        数据域                    操作                原始对象和克隆对象相互影响

      基本类型      将各个域进行拷贝               不会

      引用类型      引用同一个子对象             分情况

Employee类:

/**
 * @author XzZhao
 */
public class Employee implements Cloneable {
    private String name;
    private double salary;
    private Date   hireDay;

    public Employee(String name, double salary) {
        super();
        this.name = name;
        this.salary = salary;
    }

    public void raiseSalary(double byPercent) {
        double raise = salary * byPercent / 100;
        salary += raise;
    }

    /**
     * 浅拷贝
     * 
     * @see java.lang.Object#clone()
     */
    @Override
    public Employee clone() throws CloneNotSupportedException {
        return (Employee) super.clone();
    }

    /******* get set *********/
}
Test:
public static void main(String[] args) {
        try {
            Employee original = new Employee("John", 50000);
            Employee copy = original.clone();
            copy.raiseSalary(10);
            System.out.println(original.getSalary());// original unchanged
        } catch (CloneNotSupportedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }


        如果进行浅拷贝会发生什么?这要根据具体情况而定。如果原始对象与浅克隆对象共享的子对象是不可变的,将不会产生任何问题。也确是存在这种情形。例如,子对象属于String类这样的不允许改变的类;也有可能子对象在其生命周期内不会发生变化,既没有更改它们的方法,也没有创建对它引用的方法。

        然而,更常见的情况是子对象可变,因此必须重新定义clone方法,以便实现克隆子对象的深拷贝。Employee类中的hireDay域属于Date类,这就是一个可变的子对象。


深拷贝


         上面的clone方法并没有在Object.clone 提供的浅拷贝基础上增加任何新的功能,而只是将它声明为public。为了实现深拷贝,必须克隆所有可变的实例域。

         数据域                    操作                原始对象和克隆对象相互影响

         基本类型      将各个域进行拷贝                       不会

         引用类型      克隆所有可变的实例域               不会

Employee类:

/**
 * @author XzZhao
 */
public class Employee implements Cloneable {
    private String name;
    private double salary;
    private Date   hireDay;

    public Employee(String name, double salary) {
        super();
        this.name = name;
        this.salary = salary;
        this.hireDay = new Date();
    }

    public void raiseSalary(double byPercent) {
        double raise = salary * byPercent / 100;
        salary += raise;
    }

    /**
     * 深拷贝
     * 
     * @see java.lang.Object#clone()
     */
    @Override
    public Employee clone() throws CloneNotSupportedException {
        Employee cloned = (Employee) super.clone();
        // clone mutable
        cloned.hireDay = (Date) hireDay.clone();
        return cloned;
    }

    /******* get set *********/

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public double getSalary() {
        return salary;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }

    public Date getHireDay() {
        return hireDay;
    }

    public void setHireDay(int year, int month, int day) {
        Date newHire = new GregorianCalendar(year, month - 1, day).getTime();
        this.hireDay.setTime(newHire.getTime());
    }

    @Override
    public String toString() {
        return "Employee[name=" + name + ",salary=" + salary + ",hireDay=" + hireDay + "]";
    }

}

Test:
public static void main(String[] args) {
        try {
            Employee original = new Employee("John", 50000);
            original.setHireDay(2014, 1, 1);
            Employee copy = original.clone();
            copy.raiseSalary(10);
            copy.setHireDay(2015, 12, 1);
            System.out.println("original=" + original);
            System.out.println("copy=" + copy);
        } catch (CloneNotSupportedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

       上面的实例克隆了一个Employee对象,然后,调用两个改变域值的方法。raiseSalary方法改变了salary域值,setHireDay方法改变了hireDay域的状态。由于实现了深拷贝,所以对这两个域值的改变并没有影响原始数据。


使用序列号深拷贝

        序列号机制提供了一种克隆对象的简便途径,只要对应的类是可序列号的即可。直接将对象序列化到输出流中,然后再将其读回。这样产生的新对象是对现有对象的一个深拷贝,但是尽管这个方法很灵活,但是它通常会比显示地构建新对象并复制或克隆数据域的克隆方法慢得多。


SerialCloneable类:

/**
 * @author XzZhao
 */
public class SerialCloneable implements Cloneable, Serializable {

    @Override
    public Object clone() throws CloneNotSupportedException {

        ByteArrayOutputStream bout = new ByteArrayOutputStream();
        try {
            ObjectOutputStream out = new ObjectOutputStream(bout);
            out.writeObject(this);
            out.close();

            ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray());
            ObjectInputStream in = new ObjectInputStream(bin);
            Object ret = in.readObject();
            in.close();
            return ret;
        } catch (IOException | ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            return null;
        }
    }
}

Employee类:

/**
 * @author XzZhao
 */
public class Employee extends SerialCloneable {
    private String     name;
    private double     salary;
    private final Date hireDay;

    public Employee(String name, double salary) {
        super();
        this.name = name;
        this.salary = salary;
        this.hireDay = new Date();
    }

    public void raiseSalary(double byPercent) {
        double raise = salary * byPercent / 100;
        salary += raise;
    }

    /******* get set *********/

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public double getSalary() {
        return salary;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }

    public Date getHireDay() {
        return hireDay;
    }

    public void setHireDay(int year, int month, int day) {
        Date newHire = new GregorianCalendar(year, month - 1, day).getTime();
        this.hireDay.setTime(newHire.getTime());
    }

    @Override
    public String toString() {
        return "Employee[name=" + name + ",salary=" + salary + ",hireDay=" + hireDay + "]";
    }

}

Test:

    public static void main(String[] args) {
        try {
            Employee original = new Employee("John", 50000);
            original.setHireDay(2014, 1, 1);
            Employee copy = (Employee) original.clone();
            copy.raiseSalary(10);
            copy.setHireDay(2015, 12, 1);
            System.out.println("original=" + original);
            System.out.println("copy=" + copy);
        } catch (CloneNotSupportedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

小结


当我们进行对象克隆,都需要作出下列判断:

  • 是否不应该使用clone。
  • 默认的clone方法是否满足要求。
  • 默认的clone方法是否能够通过调用可变子对象的clone得到修补。

实际上,大多数是第一种情况,如果要使用clone方法,类必须:

  • 实现Cloneable接口。
  • 使用public方法修饰符重新定义clone方法。



         

### Java深拷贝浅拷贝的区别 在Java编程语言中,当涉及到对象的复制时,区分浅拷贝深拷贝至关重要。这两种方式处理对象及其内部成员变量的方式不同。 #### 浅拷贝 (Shallow Copy) 浅拷贝是指创建一个新的对象,该对象会拥有原有对象属性值的一份精确拷贝。对于基本数据类型的字段,其值会被直接复制到新对象中;而对于引用类型的数据,则只是简单地复制了引用地址而非实际的对象实例[^1]。这意味着两个对象将共享同一个被引用的对象,在这种情况下改变任何一个对象所持有的引用可能会间接影响另一个对象的状态。 ```java public class ShallowCopyExample implements Cloneable { private int value; private Object reference; @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } } ``` #### 深拷贝 (Deep Copy) 相比之下,深拷贝不仅复制了源对象本身还对其所有的子对象进行了独立副本的创建[^4]。因此通过这种方式得到的新对象其原型之间没有任何关联——即它们各自拥有一套完全独立的数据结构。这使得我们可以安全地操作这些副本而不用担心会影响到原来的对象。 要实现深拷贝通常有两种常见途径: - **序列化机制**:利用`Serializable`接口配合输入输出流来完成整个对象图谱的持久化过程后再反序列化回内存形成全新的实体。 ```java import java.io.*; class DeepCopyBySerialization implements Serializable { public static <T extends Serializable> T deepCopy(T object) { try { ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream out = new ObjectOutputStream(bos); out.writeObject(object); ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); ObjectInputStream in = new ObjectInputStream(bis); return (T)in.readObject(); } catch (IOException | ClassNotFoundException e) { throw new RuntimeException(e); } } } ``` - **手动克隆**:针对复杂嵌套结构的手工编写构造函数或工厂方法逐层递归构建每一个组成部分直至最底层节点为止。 ```java public final class DeepCloneableObject { private String name; private transient List<String> items; // 使用transient修饰符忽略某些不需要参深拷贝的过程 public DeepCloneableObject(String name, List<String> items){ this.name=name; if(items!=null)this.items=new ArrayList<>(items);else this.items=null; } public DeepCloneableObject deepClone(){ return new DeepCloneableObject(this.name,this.items==null? null :new ArrayList<>(this.items)); } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值