看到集合中的类都继承了Cloneable接口,便学习了Java中的浅拷贝和深拷贝.
简单变量复制
int num1 = 5;
int num2 = 0;
num1 = num2;
System.out.println("num1 = " + num1 + " num2 = " + num2);
num2 = 112;
System.out.println("num1 = " + num1 + " num2 = " + num2);
输出
num1 = 0 num2 = 0
num1 = 0 num2 = 112
很显然,这里是对其值进行了复制,当对num2进行更改时,num1直不会进行改变.所有的基本类型(boolean,char,byte,short,float,double.long)以及其包装类型和String都是如此.
假如将上面的基本类型换成其包装类型Integer,结果是一样的,但是本质上有区别
当执行Integer类型的num2 = 112;,创建的是一个新的对象Integer(112),
对象复制
创建Student类
class Student {
private int number;
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
}
编写测试代码
Student stu1 = new Student();
stu1.setNumber(12345);
Student stu2 = stu1;
System.out.println("学生1:" + stu1.getNumber());
System.out.println("学生2:" + stu2.getNumber());
stu1.setNumber(1234);
System.out.println("学生1:" + stu1.getNumber());
System.out.println("学生2:" + stu2.getNumber());
System.out.println("stu1和stu2是同一个对象么?" + (stu1 == stu2)); // true
输出
学生1:12345
学生2:12345
学生1:1234
学生2:1234
stu1和stu2是同一个对象么?true
从上面结果可以看出
当执行stu1.setNumber(1234);后,stu2的number也更改为1234.
但这就存在问题了,因为我们需要的是对象内属性的复制,但是使用Student stu2 = stu1; 后,两者对象貌似关联到一起了.
原因是执行Student stu2 = stu1; 时,是将stu2的引用指向了stu1内存地址,两者的内存地址一致,因此当更改其中一个对象的属性值时,另一个也会跟着改变.
使用clone()进行对象复制(浅拷贝实现)
在这里我们使用clone进行拷贝,clone() 是Object中定义的protected方法,对继承类可见,我们需要重写它.重写类要实现Cloneable接口,否则会报CloneNotSupportedException异常.Cloneable是一个空的接口,并没有任何接口方法.
protected native Object clone() throws CloneNotSupportedException;
public interface Cloneable {
}
class Apple implements Cloneable{
//需要实现Cloneable
private int number;
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
//重写clone()
@Override
public Object clone() {
Apple apple = null;
try {
apple = (Apple)super.clone();
return apple;
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
return apple;
}
}
测试
Apple apple1 = new Apple();
apple1.setNumber(12345);
Apple apple2;
apple2 = (Apple)apple1.clone();
System.out.println("苹果1:" + apple1.getNumber());
System.out.println("苹果2:" + apple2.getNumber());
apple1.setNumber(1234);
System.out.println("苹果1:" + apple1.getNumber());
System.out.println("苹果2:" + apple2.getNumber());
System.out.println("apple1和apple2是同一个对象么?" + (apple1 == apple2)); // false
输出
苹果1:12345
苹果2:12345
苹果1:1234
苹果2:12345
apple1和apple2是同一个对象么?false
从输出结果上看,已经实现了对象的拷贝,当执行apple1.setNumber(1234);时,apple2的值并没有进行改变.
但是事情到这里就结束了么??
Apple中的属性都为基本数据类型,当存在引用对象时,结果会怎么表现??
对包含引用对象的对象使用clone进行复制
创建包含引用对象的类,该类中包含了People引用属性
class House implements Cloneable{
private int number;
private People people;
public People getPeople() {
return people;
}
public void setPeople(People people) {
this.people = people;
}
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
@Override
public Object clone() {
House house = null;
try {
house = (House)super.clone();
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
//house.people = (People)people.clone();
return house;
}
@Override
public String toString() {
return ("number = " + number
+ " name = " + people.getName());
}
}
创建People类
class People {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
编写测试
People people = new People();
people.setName("张三");
House house1 = new House();
house1.setPeople(people);
house1.setNumber(12345);
House house2;
house2 = (House)house1.clone();
System.out.println("房子1:" + house1.toString());
System.out.println("房子2:" + house2.toString());
System.out.println("修改house1....");
house1.setNumber(2222);
people.setName("李四");
System.out.println("房子1:" + house1.toString());
System.out.println("房子2:" + house2.toString());
System.out.println("house1和house2是同一个对象么?" + (house1 == house2)); // false
输出
房子1:number = 12345 name = 张三
房子2:number = 12345 name = 张三
修改house1....
房子1:number = 2222 name = 李四
房子2:number = 12345 name = 李四
house1和house2是同一个对象么?false
从输出来看,当执行完以下语句后
house1.setNumber(2222);
people.setName(“李四”);
house2的number值未改变,因为我们已经对其实现了浅拷贝,但是people的name改变了.
原因是浅复制只是复制了people变量的引用,并没有真正的开辟另一块空间,将值复制后再将引用返回给新对象
深拷贝实现
因此如果需要实现真正的复制对象,也需要将复制对象内的引用变量实现可复制话,需要对这两个类进行改写
People也重写clone方法
class People implements Cloneable {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public Object clone() {
People people = null;
try {
people = (People)super.clone();
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
finally {
}
return people;
}
}
对House类的clone进行修改
@Override
public Object clone() {
House house = null;
try {
house = (House)super.clone();
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
//people也进行复制
house.people = (People)people.clone();
return house;
}
输出
房子1:number = 12345 name = 张三
房子2:number = 12345 name = 张三
修改house1....
房子1:number = 2222 name = 李四
房子2:number = 12345 name = 张三
house1和house2是同一个对象么?false
可以看出对house1的name进行修改后house2的name 并没有实现更改.已经实现了深度复制.
总结
**基本对象拷贝:**执行拷贝后,两个对象都指向同一个内存空间,改变其中一个对象的属性值时,另一个也跟着改变
浅拷贝: 是指在拷贝对象时,对于基本数据类型的变量会重新复制一份,而对于引用类型的变量只是对引用进行拷贝,没有对引用指向的对象进行拷贝。也就是说两个对象内的引用类型变量仍然只指向同一快内存区域.需要实现Cloneable接口
深拷贝: 是指在拷贝对象时,同时会对引用指向的对象进行拷贝。区别就在于是否对 对象中的引用变量所指向的对象进行拷贝.其本身以及内部的引用变量需要实现Cloneable接口.
在java中 clone为什么要用super.clone()方法 这里为什么要用super不是this?
1.Object中的clone执行的时候使用了RTTI(run-time type identification)的机制,动态得找到目前正在调用clone方法的那个reference,根据它的大小申请内存空间,然后进行bitwise的复制,将该对象的内存空间完全复制到新的空间中去,从而达到shallowcopy的目的。
所以你调用super.clone() 得到的是当前调用类的副本,而不是父类的副本。根本没有必用调用this.clone();
2.要让实例调用clone方法就需要让此类实现Cloneable接口,API里面还有句话是:如果在没有实现 Cloneable 接口的实例上调用 Object 的 clone 方法,则会导致抛出 CloneNotSupportedException 异常,这便是“合法”的含义。 但请注意,Cloneable接口只是个标签接口,不含任何需要实现的方法,就像Serializable接口一样。
总之,一般如果你的子类没有特殊需要而重写clone()方法就直接用super.clone() 就行了。
super.clone()的操作是:(还是以Object中的clone()来说明吧
<1>检查当前对象this所属的类(指:你的Strings类有没有实现Cloneable接口,若没有则抛出:CloneNotSupportedException
<2>创建当前对象this所属的类(即你的Strings类)的一个对象(称为:copy_object),并对该copy_object进行初始化,使得copy_object的数据成员值与当前对象this中数据成员值一模一样(即:当前对象this的二进制拷贝)–这就是所谓的"浅复制". 对于你的代码,即:copy_object对象中的数据成员private String str; 与被复制的当前对象this中的private String str 引用的是同一个字符串对象.由于:字符串对象是内部值不可改变的常对象,因而:对于浅复制,这是没有问题的.使用效果与"深复制"相同. 同样:基本数据类型的数据成员,"浅复制"与"深复制"使用效果也相同.