引入

若需修改一个对象,同时不想改变调用者的对象,就要制作该对象的一个本地副本。

分类

由于Java数据类型分为基本数据类型和引用数据类型。因此,基于两种数据类型的对象分为浅复制(Shallow Copy)深复制(Deep Copy)。

下文中将举例对两种方法做具体讲解。至于基本数就类型的复制,由于其是值传递,用“=”即可达到目的。

本文引例

首先这里的需求是:

员工信息类Employee,其中包含了员工基本信息:姓名name,年龄age,地址add三个域。

其中,姓名、年龄是String类型;

地址是一个Address类对象。

Address类,包含了国家、省份、城市。

以上三个域都是String类型。

在主方法中:

  1. 测试新建并初始化一个Employee对象e1
  2. 再声明一个Employee对象e2,e2是e1的一个复制对象。
  3. 打印两个员工的信息
  4. 改变e2中地址信息,姓名、年龄信息
  5. 再次打印两个员工基本信息,比较来两次的变化。

注:这里需要达到的是改变e2信息,e1的信息不改变。

浅复制

设计一个Employee类,源代码如下:

public class Employee implements Cloneable { 
    private String name; 
    private int age; 
    private Address add; 
    public Employee(String name, int age, Address add) { 
        super(); 
        this.name = name; 
        this.age = age; 
        this.add = add; 
    } 
    @Override 
    public String toString() { 
        StringBuilder sb=new StringBuilder(); 
        sb.append("Name: "+this.name+"---"); 
        sb.append("Age: "+this.age+"\n"); 
        sb.append("Address: "+this.add); 
        return sb.toString(); 
    } 
    @Override 
    protected Object clone() throws CloneNotSupportedException { 
        Employee e=null; 
        e=(Employee)super.clone(); 
        return e; 
    } 
    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 getAdd() { 
        return add; 
    } 
    public void setAdd(Address add) { 
        this.add = add; 
    } 
}

 

以上代码中,方法clone() 返回的对象应该独立于该对象(正被复制的对象)。这就是后面在主方法中实现复制操作的方法。

设计Address类,源码如下:

package com.ithaibo.clone;

public class Address { 
    private String state; 
    private String provice; 
    private String city; 
    public Address(String state, String provice, String city) { 
        super(); 
        this.state = state; 
        this.provice = provice; 
        this.city = city; 
    } 
    @Override 
    public String toString() { 
        StringBuilder str=new StringBuilder(); 
        str.append("City: "+this.city+"---"); 
        str.append("Provice: "+this.provice+"---"); 
        str.append("State: "+this.state); 
        return str.toString(); 
    } 
    public String getState() { 
        return state; 
    } 
    public void setState(String state) { 
        this.state = state; 
    } 
    public String getProvice() { 
        return provice; 
    } 
    public void setProvice(String provice) { 
        this.provice = provice; 
    } 
    public String getCity() { 
        return city; 
    } 
    public void setCity(String city) { 
        this.city = city; 
    } 
}

 

测试函数:

public class Clone1 {

    public static void main(String[] args) { 
        Address add=new Address("China", "Sichuan", "Chengdu"); 
        Employee e1=new Employee("Andy", 24, add); 
        System.out.println("Before clone...."); 
        System.out.println("employee one: "+e1.toString()); 
        try { 
            Employee e2=(Employee) e1.clone(); 
            System.out.println("employee two: "+e2.toString()); 
            System.out.println("After clone...."); 
            e2.getAdd().setCity("Shanghai"); 
            System.out.println("employee one: "+e1.toString()); 
            System.out.println("employee two: "+e2.toString()); 
        } catch (CloneNotSupportedException e) { 
            e.printStackTrace(); 
        } 
    }
}

 

 

结果:   

Before clone.... 
employee one: Name: Andy---Age: 24 
Address: City: Chengdu---Provice: Sichuan---State: China 
employee two: Name: Andy---Age: 24 
Address: City: Chengdu---Provice: Sichuan---State: China 
After clone.... 
employee one: Name: Andy---Age: 24 
Address: City: Shanghai---Provice: Sichuan---State: China 
employee two: Name: Andy---Age: 24 
Address: City: Shanghai---Provice: Sichuan---State: China

 

结论:

这里本来只改变了e2的地址,为什么e1的地址也改变了?这样的结果不符合要求。

分析:

造成两个员工地址信息改变的原因是,这里采用的是浅复制,即在复制过程中,对于引用型变量的复制是简单的引用地址复制,而非值复制。如下图

Java clone 深浅 java deepcopy_System

上面的途中e1、e2的address都指向了add对象所在的地址。所以在e2改变地址时,实际上改变的是add对象,因此e1的地址也改变了。

深复制

为了解决这个问题

  1. 需要对Address类进行一些修改:需要Address类实现Cloneable接口,并重写clone方法。其具体实现类似于Employee的相关代码块。

这里只对修改处作简单的陈述:

public class Address implements Cloneable {

….

@Override 
    protected Object clone() throws CloneNotSupportedException { 
        Address2 add=null; 
        add=(Address2)super.clone(); 
        return add; 
    }

….

}

 

   2.   在Employee类中的clone方法中需要调用Address中的clone对地址进行复制。

public class Employee implements Cloneable {

….

@Override 
    protected Object clone() throws CloneNotSupportedException { 
        Employee e=null; 
        e=(Employee)super.clone(); 
        e.add=(Address) add.clone(); 
        return e; 
    }

…

}

 

测试结果:

After clone.... 
employee one: Name: Andy---Age: 24 
Address: City: Chengdu---Provice: Sichuan---State: China 
employee two: Name: Andy---Age: 24 
Address: City: Shanghai---Provice: Sichuan---State: China

 

满足需求。

特别说明:在以上的复制中,虽然Employee类和Address类中的域都有String类型,String类型不属于基本数据类型。但是,String类型是一个final类。对于有关键字final的对象也不需要作深复制。