浅拷贝
什么是浅拷贝
浅拷贝会将原对象中的基本类型的变量的值和引用类型变量的值复制到新的对象中。
浅复制得到的新对象内部的引用对象与原对象的指向同一个引用的对象。
如何实现浅拷贝
- 重写Object类的clone()方法
protected native Object clone() throws CloneNotSupportedException;
该方法是一个本地(native)方法,对于任何对象 x ,clone()操作有如下特点:
- 表达式
x.clone() != x
结果应为true 。 说明拷贝后得到的是一个新的对象,而不是对原对象的引用。 - 表达式
x.clone().getClass() == x.getClass()
结果应该为true。说明拷贝得到的对象与原对象是同一类型,但这并不是必须的。 - 表达式
x.clone().equals(x)
结果应该为true,但这不是必须的。
浅拷贝实现及现象
实现对Person类的实例的浅拷贝。
-
代码结构
代码结构
- 实现
Cloneable
接口
该接口是一个标记接口(类似Serializable
),标识该类的实例可以执行clone()操作,若不实现该接口,执行clone()操作会抛出CloneNotSupportedException
异常。
public class Person implements Cloneable {
private int age; // 年龄
private String name; // 姓名
private Address address; // 地址信息
public Person(int age, String name, Address address) {
this.age = age;
this.name = name;
this.address = address;
}
// getter / setter
@Override
public String toString() {
return "name: " + name + " -- age: " + age + " -- home: " + address.getHome();
}
@Override
public Person clone() {
Person person = null;
try {
person = (Person) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return person;
}
}
- 重写Object类的clone()方法
@Override
public Person clone() {
Person person = null;
try {
person = (Person) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return person;
}
在重写clone()方法时,主要做了这些改动:
- 将方法声明为
public
(Object类中是protected
) - 调用
super.clone();
在运行时刻,Object中的 clone() 识别出你要复制的是哪一个对象,然后为此对象分配空间,并进行对象的复制,将原始对象的内容一一复制到新对象的存储空间中。 - 将拷贝后的对象转为目标类型,并返回。
- Address类代码如下
public class Address {
private String home; // 住址
// getter / setter
}
- Run类代码如下
public class Run {
public static void main(String[] args) {
Address address = new Address("beijing");
Person person = new Person(18, "wj", address);
Person person2 = person.clone();
System.out.println("Person1: " + person);
System.out.println("Person2: " + person2);
System.out.println();
System.out.println("---------------");
System.out.println();
person.setAge(30);
address.setHome("shanghai");
System.out.println("Person1: " + person);
System.out.println("Person2: " + person2);
}
}
- 执行结果
Person1: name: wj -- age: 18 -- home: beijing
Person2: name: wj -- age: 18 -- home: beijing
---------------
Person1: name: wj -- age: 30 -- home: shanghai
Person2: name: wj -- age: 18 -- home: shanghai
此时可以发现:
- person1执行clone()方法后得到person2对象,两个对象中的内容一模一样。
- 当修改person1的年龄(基本类型 int)后,person2并没有受到影响。
- 当修改person1引用的address对象(引用类型)的值后,person2的值也跟着改变。这也说明浅复制中新对象与原对象内部引用的其他对象指向的是同一个对象。
如果想在修改person1的address的值而不影响person2,则需要使用深拷贝。
深拷贝
与浅拷贝不同,深拷贝会将对象中引用的其他对象也复制一份新的实例出来分配给拷贝得到的新对象,而不仅仅是复制地址引用。
这也是深拷贝与浅拷贝最根本的不同。
如何实现深拷贝
- 同时拷贝引用的对象
我们需要在拷贝原对象的同时将其引用的其他对象也进行拷贝。如上面的例子,Person中引用了Address,那么Address也需要实现Cloneable
接口,并重写clone()
方法,在Person拷贝的同时执行拷贝操作。
- Address修改后的代码
public class Address implements Cloneable {
private String home; // 住址
// getter / setter
@Override
public Address clone() {
Address address = null;
try {
address = (Address) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return address;
}
}
- Person修改后的代码,主要修改clone()方法,其他不变
@Override
public Person clone() {
Person person = null;
try {
person = (Person) super.clone();
if (address != null) {
person.address = address.clone();
}
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return person;
}
- 测试结果
Person1: name: wj -- age: 18 -- home: beijing
Person2: name: wj -- age: 18 -- home: beijing
---------------
Person1: name: wj -- age: 30 -- home: shanghai
Person2: name: wj -- age: 18 -- home: beijing
- 使用序列化实现
方法1解决了眼前的问题,如果Address中也包括其他对象的引用,其他对象又包括其他对象的引用,那么为了实现深拷贝,我们需要不断的逐层重复方法1的操作(多层克隆问题)。
为了解决多层克隆问题,我们可以使用序列化的方式来实现深层克隆。
- 实现序列化接口Serializable
所有参与序列化的类都需要实现Serializable接口。 - 实现序列化克隆
public Person personClone() {
ByteArrayOutputStream baos = null;
ObjectOutputStream oos = null;
ByteArrayInputStream bais = null;
ObjectInputStream ois = null;
try {
baos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(baos);
oos.writeObject(this);
bais = new ByteArrayInputStream(baos.toByteArray());
ois = new ObjectInputStream(bais);
return (Person) ois.readObject();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
release(baos);
release(oos);
release(bais);
release(ois);
}
return null;
}
private void release(Closeable closeable) {
try {
if (closeable != null) {
closeable.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
- 测试
Person person2 = person.personClone();
Person1: name: wj -- age: 18 -- home: beijing
Person2: name: wj -- age: 18 -- home: beijing
---------------
Person1: name: wj -- age: 30 -- home: shanghai
Person2: name: wj -- age: 18 -- home: beijing
网友评论