Java 拷贝
一、引用拷贝
引用拷贝是不会创建新对象的,而是创建这个对象的另一个引用,最终两个引用指向的是同一个对象
如图:
我们通过一个实际的例子来看看:
// Student 类
public class Student {
String name;
int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
}
public class test {
public static void main(String[] args) {
Student s1 = new Student("student1", 12);
Student s2 = s1; // 此处就是引用拷贝
s1.age = 22;
System.out.println(s1);
System.out.println(s2);
System.out.println("s1的age:" + s1.age);
System.out.println("s2的age:" + s2.age);
System.out.println("s1==s2 " + (s1 == s2)); //相等
}
}
// 运行结果
Student@1b6d3586
Student@1b6d3586
s1的age:22
s2的age:22
s1==s2 true
从运行的结果就可以看出,它们其中一个引用修改对象的值,那么另个引用的值也相应的改变
二、浅拷贝
浅拷贝会创建一个新对象,新对象和原对象本身没有任何关系,新对象和原对象不等,但是新对象的属性和老对象相同。具体可以看如下区别:
- 如果属性是基本类型,拷贝的就是基本类型的值;
- 如果属性是引用类型,拷贝的就是内存地址(即复制引用但不复制引用的对象) ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。
如图所示:
浅拷贝的实现就是在需要拷贝的类上实现 Cloneable 接口并重写其 clone() 方法。
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
我们还是用一个实际的例子看看吧:
// Person 类
class Person {
String name;
public Person(String name) {
this.name = name;
}
@Override
public String toString() {
return "Father{" +
"name='" + name + '\'' +
'}';
}
}
// Student 类
class Student implements Cloneable {
int age;
String name;
Person person;
public Student(String name, int age) {
this.age = age;
this.name = name;
}
public Student(String name, int age, Person person) {
this.age = age;
this.name = name;
this.person = person;
}
@Override
public String toString() {
return "Son{" +
"age=" + age +
", name='" + name + '\'' +
", person=" + person +
'}';
}
@Override
protected Student clone() throws CloneNotSupportedException {
return (Student) super.clone();
}
}
public class test {
public static void main(String[] args) throws CloneNotSupportedException {
Person f = new Person("王冰冰");
Student s1 = new Student("何同学", 13);
s1.person = f;
Student s2 = s1.clone(); // 浅拷贝
System.out.println(s1);
System.out.println(s2);
System.out.println("s1==s2:" + (s1 == s2)); // 不相等
System.out.println("s1.name==s2.name:" + (s1.name == s2.name)); // 相等
System.out.println();
//但是他们的person person 和String name的引用一样
s1.age = 12;
s1.person.name = "王冰冰"; // s1.person引用未变
s1.name = "稚晖君"; // 这里改变了引用指向
System.out.println("s1.person==s2.person:" + (s1.person == s2.person)); // 相等
System.out.println("s1.name==s2.name:" + (s1.name == s2.name)); // 不相等
System.out.println(s1);
System.out.println(s2);
}
}
// 运行结果
Son{age=13, name='何同学', person=Father{name='王冰冰'}}
Son{age=13, name='何同学', person=Father{name='王冰冰'}}
s1==s2:false
s1.name==s2.name:true
s1.person==s2.person:true
s1.name==s2.name:false
Son{age=12, name='稚晖君', person=Father{name='王冰冰'}}
Son{age=13, name='何同学', person=Father{name='王冰冰'}}
从运行结果可以看出,我们 clone() 了一个 student,但是 s1 和 s2 它们的引用变量指向的还是同一个对象。此时有同学可能会问:第二次运行的 s1.name == s2.name : false,因为这里 name 初始==是相等的,当我们执行 s1.name = "稚晖君" 后,会改变 s1 的 name 的指向,如图:
小贴士:建议大家把代码复制运行一下,这样更加清楚
三、深拷贝
深拷贝:在对引用数据类型进行拷贝的时候,创建了一个新的对象,并且复制其内的成员变量。
如图:
如何实现呢?可以重写 clone() 方法和序列化
重写clone()方法
如果使用重写clone()方法实现深拷贝,那么要将类中所有自定义引用变量的类也去实现Cloneable接口实现clone()方法。对于字符类可以创建一个新的字符串实现拷贝。
代码如下:
//Person 类实现 Cloneable 接口并重写 clone() 方法。student 的clone()方法需要对各个引用都拷贝一遍。
//Person clone()方法
@Override
protected Person clone() throws CloneNotSupportedException {
return (Person) super.clone();
}
//student clone()方法
@Override
protected Student clone() throws CloneNotSupportedException {
Student student= (Student) super.clone();//待返回克隆的对象
student.name=new String(name);
student.person=person.clone();
return student;
}
其他代码不变,执行结果如下:
Son{age=13, name='何同学', person=Father{name='王冰冰'}}
Son{age=13, name='何同学', person=Father{name='王冰冰'}}
s1==s2:false
s1.name==s2.name:false
s1.person==s2.person:false
s1.name==s2.name:false
Son{age=12, name='稚晖君', person=Father{name='王冰冰'}}
Son{age=13, name='何同学', person=Father{name='王冰冰'}}
从结果可以看出,s1 的 name 和 s2 的 name 不相等,s1 的 person 和 s2 的 person 也不相等,所以可以看出深拷贝是对引用数据类型也进行拷贝
序列化
序列化相信大家都不陌生吧,其实序列化是利用了深拷贝。我们只需要自定义的类实现 Serializable 接口即可。
代码如下:
protected Son deepClone() throws IOException, ClassNotFoundException {
Son son=null;
//在内存中创建一个字节数组缓冲区,所有发送到输出流的数据保存在该字节数组中
//默认创建一个大小为32的缓冲区
ByteArrayOutputStream byOut=new ByteArrayOutputStream();
//对象的序列化输出
ObjectOutputStream outputStream=new ObjectOutputStream(byOut);//通过字节数组的方式进行传输
outputStream.writeObject(this); //将当前student对象写入字节数组中
//在内存中创建一个字节数组缓冲区,从输入流读取的数据保存在该字节数组缓冲区
ByteArrayInputStream byIn=new ByteArrayInputStream(byOut.toByteArray()); //接收字节数组作为参数进行创建
ObjectInputStream inputStream=new ObjectInputStream(byIn);
son=(Son) inputStream.readObject(); //从字节数组中读取
return son;
}
// 执行结果:
Son{age=13, name='何同学', person=Father{name='王冰冰'}}
Son{age=13, name='何同学', person=Father{name='王冰冰'}}
s1==s2:false
s1.name==s2.name:false
s1.person==s2.person:false
s1.name==s2.name:false
Son{age=12, name='稚晖君', person=Father{name='王冰冰'}}
Son{age=13, name='何同学', person=Father{name='王冰冰'}}
综上:所以序列化也是利用了深拷贝!!!
引用:
https://segmentfault.com/a/1190000038523408
https://segmentfault.com/a/1190000018646675?utm_source=sf-similar-article