相信大家平时写代码经常会需要复制一份属性或对象,那么今天,我们就来讨论一下Java中常用的复制方法。
一、复制基本类型
对于基本类型来说,我们可以使用=号来进行复制,例如:
int a = 25;
int b = a;
上面b复制了a的值为25
二、复制对象
1、等号方法
创建一个Address类,它拥有一个addName属性
public class Address implements Cloneable {
private String addName;
public String getAddName() {
return addName;
}
public void setAddName(String addName) {
this.addName = addName;
}
@Override
public String toString() {
return "Address{" +
"地址='" + addName + '\'' +
'}';
}
}
如何复制一份address对象呢,有的同学说,这还不容易吗,也用=号复制就行:
Address address = new Address();
address.setAddName("陋室");
Address addressC = address;
System.out.println("复制前:" + address.toString());
System.out.println("复制后:" + addressC.toString());
运行结果:
可以看到成功复制了address,这时候我们改变address对象的值:
address.setAddName("南阳诸葛庐");
System.out.println("属性改变");
System.out.println("复制前:" + address.toString());
System.out.println("复制后:" + addressC.toString());
运行结果:
我们想改变address对象的值,却把addressC的值一同改变了,这是为什么呢?
原因:使用等号复制时,对于值类型来说,彼此之间的修改操作是相对独立的,而对于引用类型来说,因为复制的是引用对象的内存地址,所以修改其中一个属性值,另一个值也会跟着变化。
为了解决属性变化的问题,我们可以采用克隆方式来复制对象
2、克隆方法
浅克隆:克隆一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原有属性所指向的对象的内存地址。
深克隆:克隆一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址。
深浅克隆都会在堆中新分配一块区域,区别在于对象属性引用的对象是否需要进行克隆。
实现克隆的步骤(分三步):
- 对象的类实现Cloneable接口
- 覆盖Object类的clone()方法(覆盖clone()方法,访问修饰符设为public,默认是protected)
- 在clone()方法中调用super.clone()
(1)浅克隆
创建一个Student实体类,拥有姓名、年龄、地址三个属性,地址属性为上面Address类的对象,按照上述步骤实现Cloneable接口并覆盖clone方法
public class Student implements Cloneable {
private String name;
private int age;
private Address address;
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 getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
@Override
public String toString() {
return "Student{" +
"姓名='" + name + '\'' +
", 年龄=" + age +
"," + address.toString() +
'}';
}
public Student clone() throws CloneNotSupportedException {
Student student = (Student) super.clone();
return student;
}
}
在Main函数测试:
Student student = new Student();
student.setName("秦始皇");
student.setAge(40);
Address address = new Address();
address.setAddName("秦始皇陵");
student.setAddress(address);
Student studentC = student.clone();
System.out.println("克隆前:" + student.toString());
System.out.println("克隆后:" + studentC.toString());
运行结果:
可以发现我们成功克隆了Student类,下面改变Student的属性:
student.setName("嬴政");
student.setAge(25);
address.setAddName("咸阳阿房宫");
System.out.println("属性发生改变");
System.out.println("克隆前:" + student.toString());
System.out.println("克隆后:" + studentC.toString());
运行结果:
可以看到studentC对象的姓名和年龄属性并没有随着student对象的改变而改变,但是Address属性仍指向同一个引用,所以我们需要使用深克隆来进行完整的复制
(2)深克隆
我们对Student类和Adress类进行一些改造
修改Student类的clone方法,同时克隆address对象
public Student clone() throws CloneNotSupportedException {
Student student = (Student) super.clone();
student.setAddress(student.getAddress().clone());
return student;
}
让Address类实现克隆方法
public class Address implements Cloneable {
private String addName;
public String getAddName() {
return addName;
}
public void setAddName(String addName) {
this.addName = addName;
}
@Override
public String toString() {
return "Address{" +
"地址='" + addName + '\'' +
'}';
}
public Address clone() throws CloneNotSupportedException {
Address address = (Address) super.clone();
return address;
}
}
Main函数测试:
Student student = new Student();
student.setName("秦始皇");
student.setAge(40);
Address address = new Address();
address.setAddName("秦始皇陵");
student.setAddress(address);
Student studentC = student.clone();
System.out.println("克隆前:" + student.toString());
System.out.println("克隆后:" + studentC.toString());
student.setName("嬴政");
student.setAge(25);
address.setAddName("咸阳阿房宫");
System.out.println("属性发生改变");
System.out.println("克隆前:" + student.toString());
System.out.println("克隆后:" + studentC.toString());
运行结果:
可以看到,student被完全克隆成studentC,并且它的属性不随着原对象的改变而改变
3、流处理方式
我们也可以使用流处理来复制对象
Student student = new Student();
student.setName("秦始皇");
student.setAge(40);
Address address = new Address();
address.setAddName("秦始皇陵");
student.setAddress(address);
//将对象写到流里
ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
ObjectOutputStream objOut = new ObjectOutputStream(byteOut);
objOut.writeObject(student);
//从流里读出来
ByteArrayInputStream byteIn = new ByteArrayInputStream(byteOut.toByteArray());
ObjectInputStream objInput = new ObjectInputStream(byteIn);
Student studentC = (Student) objInput.readObject();
System.out.println("克隆前:" + student.toString());
System.out.println("克隆后:" + studentC.toString());
student.setName("嬴政");
student.setAge(25);
address.setAddName("咸阳阿房宫");
System.out.println("属性发生改变");
System.out.println("克隆前:" + student.toString());
System.out.println("克隆后:" + studentC.toString());
运行结果:
可以看到结果与深克隆一致。
三、小结
复制的方式有很多,我们应该结合场景灵活地选取方法来实现复制,如果不改变对象属性,我们可以直接用等号复制;对象只有基本类型的属性,我们可以使用浅克隆方式;对象引用属性层数浅,可以使用深克隆方式;对象引用属性层数深,建议使用对象流处理方式。
参考链接:
https://my.oschina.net/u/4391811/blog/3252764
https://www.cnblogs.com/Qian123/p/5710533.html
https://www.cnblogs.com/liqiangchn/p/9465186.html