Java中的数据类型分为基本数据类型和引用数据类型。
基于基本数据类型和引用类型拷贝时候的数据传递,Java对于拷贝,分为浅拷贝(swallow copy)和深拷贝(deep copy)。
看一个例子,一个Student对象,里面有三个属性,一个String型name,一个Address对象型address,一个集合型hobbies。
public class Student {
// 姓名- 简单属性
private String name;
// 地址- 对象
private Address address;
// 爱好- 集合类型
private List<String> hobbies;
public Student(String name, Address address, List<String> hobbies) {
this.name = name;
this.address = address;
this.hobbies = hobbies;
}
...省略get set方法...
public String toString() {
return "[name:"+name+";address:" + address.toString()+";hobbies:" + hobbies.toString() + "]";
}
}
附 Address类定义代码,只是为了说明问题,这里Address对象里面仅有一个String型的provice信息。
public class Address {
// 省
private String provice;
public Address(String provice) {
this.provice = provice;
}
...省略get set方法...
public String toString() {
return "[provice:"+ provice+"]";
}
}
浅拷贝
浅拷贝说明
- 对于数据类型是基本数据类型的成员变量,浅拷贝时候会直接复制一份属性值给到新对象。
- 对于数据类型是引用数据类型的成员变量,比如说成员变量是集合列表、某个类的对象,那么浅拷贝会进行引用传递,就是将该成员变量的引用值(内存地址)复制一份给新的对象。因为实际上两个对象的该成员变量都指向同一个实例,在一个对象中修改该成员变量会影响到另一个对象的该成员变量值。
浅拷贝的实现通常有两种
- 构造方法拷贝 ——使用对象的构造方法,将原对象的各属性值传递过去,获得新的对象。
- 普通重写clone()方法 ——通过实现clone方法,直接克隆一个新的对象。
基于上面的Student类定义,假如有一个student
List<String> hobbies = new ArrayList<>();
hobbies.add("running");
hobbies.add("skiing");
Address address = new Address("China.Taiwan");
Student stu = new Student("zhangsan", address, hobbies);
如果想基于stu对象复制一个stuCopy对象,看两种不同实现。
构造方法拷贝
构造方法拷贝,顾名思义,使用对象的构造方法,将原对象的各个属性get到赋给构造方法,构建出一个新的对象。
Student stuCopy = new Student(stu.getName(), stu.getAddress(), stu.getHobbies());
此时,修改stu的name、address和hobbies,修改后,分别打印stu和stuCopy两个对象
stu.setName("lisi");;
hobbies.add("climing");
address.setProvice("Hainan");
System.out.println(stu);
System.out.println(stuCopy);
通过控制台输出,可以看到
[name:lisi;address:[provice:Hainan];hobbies:[running, skiing, climing]]
[name:zhangsan;address:[provice:Hainan];hobbies:[running, skiing, climing]]
这里stuCopy除了name是当初拷贝的值,address和hobbies值都被stu后来的set方法给改过了。
这是因为,使用构造方法拷贝对象的时候,基本属性拷贝的是对象的值,对象属性和集合属性拷贝的是对象的引用。拷贝后,如果原对象属性做了修改,新对象的属性值会跟着一起修改。
clone方法拷贝
clone拷贝,须要对象实现cloneable接口,重写clone方法
public class Student implements Cloneable{
// 姓名- 简单属性
private String name;
// 地址- 对象
private Address address;
// 爱好- 集合类型
private List<String> hobbies;
public Student(String name, Address address, List<String> hobbies) {
this.name = name;
this.address = address;
this.hobbies = hobbies;
}
...省略get set方法...
public Student clone() throws CloneNotSupportedException {
Student stu = (Student) super.clone();
return stu;
}
public String toString() {
return "[name:"+name+";address:" + address.toString()+";hobbies:" + hobbies.toString() + "]";
}
}
重写后,再测试
public static void main(String[] args) throws CloneNotSupportedException {
List<String> hobbies = new ArrayList<>();
hobbies.add("running");
hobbies.add("skiing");
Address address = new Address("China.Taiwan");
Student stu = new Student("zhangsan", address, hobbies);
// Student stuCopy = new Student(stu.getName(), stu.getAddress(), stu.getHobbies());
Student stuCopy = stu.clone();
System.out.println(stuCopy);
stu.setName("lisi");
hobbies.add("climing");
address.setProvice("Hainan");
System.out.println(stu);
System.out.println(stuCopy);
}
此处,多增加一个初始stuCopy,通过控制台输出,可以看到,使用clone方法拷贝与构造方法拷贝结果相同。
[name:zhangsan;address:[provice:China.Taiwan];hobbies:[running, skiing]]
[name:lisi;address:[provice:Hainan];hobbies:[running, skiing, climing]]
[name:zhangsan;address:[provice:Hainan];hobbies:[running, skiing, climing]]
深拷贝
深拷贝说明
浅拷贝时候,对于基本数据类型都已经实现拷贝后修改无影响了,对引用类型的拷贝因为指向同一内存空间而没有实现彻底的隔离。若要实现深拷贝,则主要的给引用类型的属性申请到新的内存空间,让引用类型的拷贝对象不再与原对象指向同一块内存空间,以实现物理上个拷贝隔离。
深拷贝的实现通常有两种方式
- clone方法拷贝 ——对引用类型的对象依次实现clone方法
- 序列化实现 —— 把原对象写入到一个字节流中,再从字节流中将其读出来,创建一个新的对象
clone方法拷贝
使用clone方法做深拷贝的时候,须对原对象中每一个对象属性都重写clone方法。
因Student对象已经重写了clone方法,这里对Address重写clone方法
public class Address implements Cloneable {
...略...
public Address clone() throws CloneNotSupportedException {
Address addr = (Address) super.clone();
return addr;
}
public String toString() {
return "[provice:"+ provice+"]";
}
}
同步改造 Student类定义中关于Address属性的获取方式
public class Student implements Cloneable {
...略...
public Student clone() throws CloneNotSupportedException {
Student stu = (Student) super.clone();
stu.address = (Address) this.getAddress().clone();
return stu;
}
public String toString() {
return "[name:"+name+";address:" + address.toString()+";hobbies:" + hobbies.toString() + "]";
}
}
改造后,再测试
public static void main(String[] args) throws CloneNotSupportedException {
List<String> hobbies = new ArrayList<>();
hobbies.add("running");
hobbies.add("skiing");
Address address = new Address("China.Taiwan");
Student stu = new Student("zhangsan", address, hobbies);
Student stuCopy = stu.clone();
System.out.println(stuCopy);
stu.setName("lisi");
hobbies.add("climing");
address.setProvice("Hainan");
System.out.println(stu);
System.out.println(stuCopy);
}
通过控制台输出可以看到
[name:zhangsan;address:[provice:China.Taiwan];hobbies:[running, skiing]]
[name:lisi;address:[provice:Hainan];hobbies:[running, skiing, climing]]
[name:zhangsan;address:[provice:China.Taiwan];hobbies:[running, skiing, climing]]
重写Address类的clone方法后,stu对象修改address属性的时候,没有再影响到stuCopy的address属性。但是stu的hobbies属性修改,影响到了stuCopy的hobbies。这是因为hobbies的类型为List,而List对象是Java自有对象,我们不方便重写List的clone方法。如此,这里该如何处理,须要换种方式把List对象的属性值复制出来,可以使用addAll()方法复制,或使用循环方式复制。
改造Student类的clone方法
public Student clone() throws CloneNotSupportedException {
Student stu = (Student) super.clone();
stu.address = (Address) this.getAddress().clone();
List<String> hobby = new ArrayList<>();
hobby.addAll(this.getHobbies());
stu.hobbies = hobby;
return stu;
}
继续测试,可以看到,再对stu修改的时候,没有再对stuCopy的属性影响。
[name:zhangsan;address:[provice:China.Taiwan];hobbies:[running, skiing]]
[name:lisi;address:[provice:Hainan];hobbies:[running, skiing, climing]]
[name:zhangsan;address:[provice:China.Taiwan];hobbies:[running, skiing]]
Serializable序列化方式
序列化方式,继承Serializable接口进行序列化与反序列化进行深拷贝,这里要注意,深拷贝的时候,主对象里面涉及到的对象属性均须实现Serializable接口,否则,会出错。
改造Student类
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.List;
public class Student implements Cloneable, Serializable {
/**
*
*/
private static final long serialVersionUID = 9007215701712486285L;
// 姓名- 简单属性
private String name;
// 地址- 对象
private Address address;
// 爱好- 集合类型
private List<String> hobbies;
public Student(String name, Address address, List<String> hobbies) {
this.name = name;
this.address = address;
this.hobbies = hobbies;
}
...省略get set方法...
public String toString() {
return "[name:"+name+";address:" + address.toString()+";hobbies:" + hobbies.toString() + "]";
}
public Student clone () {
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
ByteArrayInputStream bais = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
return (Student) ois.readObject();
} catch (IOException|ClassNotFoundException e) {
e.printStackTrace();
}
return null;
}
}
改造 Address类定义
import java.io.Serializable;
public class Address implements Cloneable, Serializable {
/**
*
*/
private static final long serialVersionUID = 5879834545307777452L;
// 省
private String provice;
public Address(String provice) {
this.provice = provice;
}
public String getProvice() {
return provice;
}
public void setProvice(String provice) {
this.provice = provice;
}
public Address clone() throws CloneNotSupportedException {
Address addr = (Address) super.clone();
return addr;
}
public String toString() {
return "[provice:"+ provice+"]";
}
}
同步的,可以查看List接口的实现类ArrayList代码,亦实现Serializable接口
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
private static final long serialVersionUID = 8683452581122892189L;
/**
* Default initial capacity.
*/
private static final int DEFAULT_CAPACITY = 10;
...省略其它...
}
再测试,结果与使用clone方法实现深拷贝结果相同。