1.什么是拷贝?
拷贝即对已有的数据创建一个副本。Java中,拷贝可分为引用拷贝、浅拷贝、深拷贝。
2.引用拷贝
引用拷贝即直接赋值。举例如下:
User user1 = new User(1, new Address("湖南",404100));
User user2 = user1;
堆栈图如下:
3.浅拷贝
浅拷贝对于引用对象里面的基本类型的数据直接拷贝,对于引用对象里面的引用类型数据不进行拷贝。在Java里面实现浅拷贝只要实现Cloneable
接口,重写clone
方法即可,但是要注意的是clone
方法要用public
修饰。
举例如下:
code1
public class User implements Cloneable{
private String name;
private Address address;
public User(String name, Address address) {
this.name = name;
this.address = address;
}
public User() {
}
// set get...
@Override
public User clone(){
User user = null;
try {
user = (user)super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return user;
}
}
code2
public class Address implements Cloneable{
private String name;
private Integer number;
public Address(String name, Integer number) {
this.name = name;
this.number = number;
}
public Address() {
}
// set get...
@Override
public Address clone() {
Address address = null;
try {
address = (Address)super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return address;
}
}
测试code
@Test
public void testShallowCopy() {
User user1 = new User(1, new Address("湖南",404100));
User user2 = user1.clone();
// 修改基本类型数据
user1.setId(2);
user1.getAddress().setNumber(404101);
System.out.println(user1);
System.out.println(user2);
}
输出结果:
User{id=2, address=Address{name='湖南', number=404101}}
User{id=1, address=Address{name='湖南', number=404101}}
从结果中可以看出浅拷贝后修改基本类型的数据,复制后对象user2基本类型数据没有修改,但是修改对象里面的引用类型数据,两个对象都修改了。这说明浅拷贝对于引用对象里面的基本类型的数据直接拷贝,对于引用对象里面的引用类型数据不进行拷贝。
浅拷贝堆栈图如下:
4.深拷贝
深拷贝会对所有数据类型都进行拷贝一份。在Java实现深拷贝有两种方式:
- 第一种是对浅拷贝进行加强
- 第二种是通过序列化
方式1
第一种方式是对于引用对象clone
之后,再对引用对象里面的引用类型一 一clone
(这需要对应的引用类型都重写clone
方法)。举例如下:
code1
public class User implements Cloneable{
private String name;
private Address address;
public User(String name, Address address) {
this.name = name;
this.address = address;
}
public User() {
}
// set get...
@Override
public User clone(){
User user = null;
try {
user = (User)super.clone();
// 引用对象都需要clone
user.setAddress(user.address.clone());
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return user;
}
}
code2跟测试code同上浅拷贝。
输出结果:
User{id=2, address=Address{name='湖南', number=404101}}
User{id=1, address=Address{name='湖南', number=404100}}
从结果可以看出,拷贝后,无论是修改user1引用类型的数据还是修改基本数据都不会修改拷贝后的值,这说明拷贝后的user2与user1不是同一块数据。
方式2
通过序列化实现注意要给对应的类实现Serializable
接口。举例如下:
coder1
public class Address implementsSerializable {
private static final long serialVersionUID = 5374537365790314243L;
private String name;
private Integer number;
public Address(String name, Integer number) {
this.name = name;
this.number = number;
}
public Address() {
}
// 省略set get...
}
coder2
public class User implements Serializable {
private static final long serialVersionUID = 857226044350983107L;
private Integer id;
private Address address;
public User(Integer id, Address address) {
this.id = id;
this.address = address;
}
// set get...
}
测试coder
@Test
public void testDeepCopy(){
ObjectOutputStream oos = null;
User user1 = null;
try {
oos = new ObjectOutputStream(new FileOutputStream("tempFile3"));
user1 = new User(1, new Address("湖南",404100));
oos.writeObject(user1);
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
ObjectInputStream ois = null;
try {
ois = new ObjectInputStream(new FileInputStream("tempFile3"));
User user2 = (User) ois.readObject();
// 修改基本类型数据
user1.setId(2);
user1.getAddress().setNumber(404101);
System.out.println(user1);
System.out.println(user2);
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}finally {
try {
ois.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
输出结果:
User{id=2, address=Address{name='湖南', number=404101}}
User{id=1, address=Address{name='湖南', number=404100}}
结果与第一种方法一致。
拷贝堆栈图如下:
参考:
- 《 Thinking In Java》中文版