引入
若需修改一个对象,同时不想改变调用者的对象,就要制作该对象的一个本地副本。
分类
由于Java数据类型分为基本数据类型和引用数据类型。因此,基于两种数据类型的对象分为浅复制(Shallow Copy)和深复制(Deep Copy)。
下文中将举例对两种方法做具体讲解。至于基本数就类型的复制,由于其是值传递,用“=”即可达到目的。
本文引例
首先这里的需求是:
员工信息类Employee,其中包含了员工基本信息:姓名name,年龄age,地址add三个域。
其中,姓名、年龄是String类型;
地址是一个Address类对象。
Address类,包含了国家、省份、城市。
以上三个域都是String类型。
在主方法中:
- 测试新建并初始化一个Employee对象e1
- 再声明一个Employee对象e2,e2是e1的一个复制对象。
- 打印两个员工的信息
- 改变e2中地址信息,姓名、年龄信息
- 再次打印两个员工基本信息,比较来两次的变化。
注:这里需要达到的是改变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的地址也改变了?这样的结果不符合要求。
分析:
造成两个员工地址信息改变的原因是,这里采用的是浅复制,即在复制过程中,对于引用型变量的复制是简单的引用地址复制,而非值复制。如下图
上面的途中e1、e2的address都指向了add对象所在的地址。所以在e2改变地址时,实际上改变的是add对象,因此e1的地址也改变了。
深复制
为了解决这个问题
- 需要对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的对象也不需要作深复制。