1 浅拷贝
1.1 介绍
浅拷贝会创建一个新对象,新对象属性值有着原对象属性值的准确拷贝。
- 当属性是基本类型,则直接进行值传递,是两份不同的内容,当对其中一个修改,不会影响另外一个。
- 当属性是引用类型,则拷贝的是该引用类型的内存地址。
1.2 实现
Person类
public class Person {
private String name;
private int age;
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 Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person{" +
this.hashCode()+
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
Student类:
public class Student implements Cloneable{
private String name;
private int age;
private Person person;
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 Person getPerson() {
return person;
}
public void setPerson(Person person) {
this.person = person;
}
@Override
public String toString() {
return "Student{" +
this.hashCode()+
"name='" + name + '\'' +
", age=" + age +
", person=" + person +
'}';
}
@Override
protected Object clone(){
try {
return super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
return null;
}
}
}
测试类:
public class ShallowCopyDemo {
public static void main(String[] args) {
Person person = new Person("zhangsan",18);
Student s1 = new Student();
s1.setPerson(person);
s1.setName("lisi");
s1.setAge(19);
//拷贝
Student s2 = (Student) s1.clone();
s2.setPerson(person);
s2.setName("wangwu");
s2.setAge(20);
Person person1 = s2.getPerson();
person1.setName("zhaoliu");
System.out.println("s1: "+s1.toString());
System.out.println("s2: "+s2.toString());
}
}
测试结果:
s1: Student{21685669name=‘lisi’, age=19, person=Person{2133927002name=‘zhaoliu’, age=18}}
s2: Student{1836019240name=‘wangwu’, age=20, person=Person{2133927002name=‘zhaoliu’, age=18}}
根据运行结果,从s1拷贝出来的s2,两者是不同的两个对象,当对两者基本类型修改时,互不影响,相互独立。但对引用类型修改时,修改了一个,则另外一个也会发生修改。
2 深拷贝
2.1 介绍
- 当属性是基本类型时,其效果与浅拷贝一样。
- 当属性是引用类型时,会为其单独开辟一块内存空间,让两者存在于不同的内容空间,从而实现相互独立。
2.2 实现
2.2.1 基于Cloneable实现
引用类型的类上需要实现Cloneable接口,并重写clone()
Person类
public class Person implements Cloneable{
private String name;
private int age;
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 Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person{" +
this.hashCode()+
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
protected Object clone() {
try {
return super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
return null;
}
}
}
在拷贝对象上,重写clone()方法,拿到拷贝后产生的新对象,然后对新对象中的引用类型通过clone()重新赋值,从而实现对引用类型深拷贝。
public class Student implements Cloneable{
private String name;
private int age;
private Person person;
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 Person getPerson() {
return person;
}
public void setPerson(Person person) {
this.person = person;
}
@Override
public String toString() {
return "Student{" +
this.hashCode()+
"name='" + name + '\'' +
", age=" + age +
", person=" + person +
'}';
}
@Override
protected Object clone(){
try {
Student student = (Student) super.clone();
student.person = (Person) person.clone();
return student;
} catch (CloneNotSupportedException e) {
e.printStackTrace();
return null;
}
}
}
测试:
public class DeepCopy {
public static void main(String[] args) {
Person person = new Person("zhangsan",18);
Student s1 = new Student();
s1.setPerson(person);
s1.setName("lisi");
s1.setAge(19);
//拷贝
Student s2 = (Student) s1.clone();
s2.setName("wangwu");
s2.setAge(20);
Person person1 = s2.getPerson();
person1.setName("zhaoliu");
System.out.println("s1:"+s1.toString());
System.out.println("s2:"+s2.toString());
}
}
s1:Student{21685669name=‘lisi’, age=19, person=Person{2133927002name=‘zhangsan’, age=18}}
s2:Student{1836019240name=‘wangwu’, age=20, person=Person{325040804name=‘zhaoliu’, age=18}}
此时可以发现,引用类型没有因为一个改变而影响另一个,因为两个现在是不同的两个对象。
2.2.2 基于序列化实现
通过clone()的方式虽然能够实现,但是问题就是,如果是多级嵌套对象的话,通过这种方式实现来就过于繁琐了。每一个引用类型都是去实现Cloneable接口重写clone()方法。
现在也可以通过对象序列化与反序列化方式来实现深拷贝
Person类:
public class Person implements Serializable {
private String name;
private int age;
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 Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person{" +
this.hashCode()+
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
改造拷贝类,添加构造方法,让引用类型作为构造方法的参数使用
Student类:
public class Student implements Serializable{
private String name;
private int age;
private Person person;
public Student(String name, int age, Person person) {
this.name = name;
this.age = age;
this.person = person;
}
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 Person getPerson() {
return person;
}
public void setPerson(Person person) {
this.person = person;
}
@Override
public String toString() {
return "Student{" +
this.hashCode()+
"name='" + name + '\'' +
", age=" + age +
", person=" + person +
'}';
}
}
测试类:
public class DeepCopy {
public static void main(String[] args) throws IOException, ClassNotFoundException {
Person person = new Person("zhangsan",18);
Student s1 = new Student("lisi",19,person);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(s1);
oos.flush();
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(baos.toByteArray()));
Student s2 = (Student) ois.readObject();
s2.setName("wangwu");
s2.setAge(20);
Person person1 = s2.getPerson();
person1.setName("zhaoliu");
System.out.println("s1:"+s1.toString());
System.out.println("s2:"+s2.toString());
}
}
3 小结
深拷贝与浅拷贝的差异性,主要表现在对于引用类型的操作上,当使用浅拷贝,拷贝前与拷贝后对象的引用类型指向的都是同一个内存地址,当修改一个,另一个也会发生改变。而深拷贝,会让两者存在于不同的内存空间,从而实现两者的相互独立,互不影响。
浅拷贝消耗资源较低,但会造成数据不安全。深拷贝解决的数据安全的问题,但消耗大。
在决定使用深拷贝还是浅拷贝时,主要的思考点不在于效率问题,而是取决于当前的业务逻辑。