刨析Object中的clone方法,涉及浅拷贝,深拷贝,原型模式
刨析Object中的clone方法,涉及浅拷贝,深拷贝,原型模式
说到java中的克隆,必要的一个方法就是Object类中native clone方法。
protected native Object clone() throws CloneNotSupportedException;
它是Object中的方法,这意味所有的类都可以实现这一方法。因为所有的类都隐式继承了Object。
注意:对象想要使用clone()必须要继承Cloneable接口.否则将抛出CloneNotSupportedException异常.
它一般在什么场景下使用呢?
比如说我们需要许多的奖状,同一学校的“三好学生”奖状除了获奖人姓名不同,其他都相同,
难道我们要一个一个new 奖状("XX",msg); 这样一系列操作吗?
在参数较少的情况下,麻烦程度可能不太显现,那如果一个对象的参数设置就有许多,那就太麻烦了.
这时我们就可以使用clone方法快捷的创建对象。
就像这样:
Citation c1 = new Citation();
Student stu = new Student("张三", "西安");
c1.setStu(stu);
//复制奖状
Citation c2 = c1.clone();
//获取c2奖状所属学生对象
Student stu1 = c2.getStu();
stu1.setName("李四");
我们只需要复制多个“三好学生”奖 状出来,然后在修改奖状上的名字即可。
所以对于对象的创建非常复杂,我们可以使用clone()快捷的创建对象。
这也就是设计模式中原型模式的思想.
使用clone 和 浅克隆
举个例子:
我们有一个Man对象,继承Cloneable接口,重写clone方法,并使用clone方法复制.
class Man implements Cloneable {
String name;
int age;
Son son; //一个对象 其类拥有name和age属性
public Man(String name, int age, Son son) {
this.age = age;
this.name = name;
this.son = son;
}
...省略name和age,son的get set方法 和重写的toString方法
//重写clone方法
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
Main方法:
public static void main(String[] args) throws CloneNotSupportedException {
Son son = new son("小明",12);
Man man1 = new Man("明他爸",35,son);//new 一个Man对象
Man man2 = (Man) man1.clone(); //使用clone
Man man3 = (Man) man1.clone();
System.out.println(man1); //打印对象
System.out.println(man2);
System.out.println(man3);
System.out.println(man1==man2);//判断是否是同一个对象
System.out.println(man2==man3);
System.out.println(man1.son==man2.son);//判断son属性对象地址是否相同
System.out.println(man2.son==man3.son);
}
结果:
Man{name='明他爸', age=35, son=Son{name='小明', age=12}} //属性值相同
Man{name='明他爸', age=35, son=Son{name='小明', age=12}}
Man{name='明他爸', age=35, son=Son{name='小明', age=12}}
false //Main对象地址不相同
false
true //son属性对象地址相同
true
可以看到使用clone()复制的对象,它们的属性值相等,但是对象地址不相同,意味这他们拥有不同的堆空间.不是同一个对象.
但是Main类中属性Son对象却是同一个对象.他们有相同的地址.
这也就是浅克隆.
结论:单纯使用clone即super.clone();创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原有 属性所指向的对象的内存地址.
向上面使用浅克隆,复制出许多不同Man对象,而son对象却只有一个. 这么多爸爸只有一个儿子,那可就太惨了.🤣🤣🤣
当我修改对象中的非基本类型属性时,修改一个将会影响所有的克隆对象,因为对象中的非基本类型属性都是同一个.这将是一个非常严重的安全问题.
怎么解决呢?这就需要深克隆了.
深克隆是完完全全的克隆.复制整个对象信息,包含值类型和引用类型
两种深克隆的实现
深克隆的实现通常有两种方法:
- 序列化实现深克隆:先将原对象序列化到内存的字节流中,再从字节流中反序列化出刚刚存储的对象,这个新对象和原对象就不存在任何地址上的共享,这样就实现了深克隆。
- 所有引用类型都实现克隆:要复制对象的所有引用类型都要实现克隆,所有对象都是复制的新对象,从而实现了深克隆。
深克隆实现方式一:序列化(推荐)
实现思路:先将要拷贝对象写入到内存中的字节流中,然后再从这个字节流中读出刚刚存储的信息,作为一个新对象返回,那么这个新对象和原对象就不存在任何地址上的共享,自然实现了深拷贝。
注意类要实现 Serializable 接口
请参考以下代码:
class CloneTest {
public static void main(String[] args) throws CloneNotSupportedException {
BirdChild birdChild = new BirdChild();
birdChild.name = "小小鸟";
Bird bird = new Bird();
bird.name = "小鸟";
bird.birdChild = birdChild;
// 使用序列化克隆对象
Bird bird2 = CloneUtils.clone(bird);
bird2.name = "黄雀";
bird2.birdChild.name = "小黄雀";
System.out.println("bird name:" + bird.name);
System.out.println("bird child name:" + bird.birdChild.name);
System.out.println("bird name 2:" + bird2.name);
System.out.println("bird child name 2:" + bird2.birdChild.name);
}
}
class CloneUtils {
public static <T extends Serializable> T clone(T obj) {
T cloneObj = null;
try {
//写入字节流
ByteArrayOutputStream bo = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bo);
oos.writeObject(obj);
oos.close();
//分配内存,写入原始对象,生成新对象
ByteArrayInputStream bi = new ByteArrayInputStream(bo.toByteArray());//获取上面的输出字节流
ObjectInputStream oi = new ObjectInputStream(bi);
//返回生成的新对象
cloneObj = (T) oi.readObject();
oi.close();
} catch (Exception e) {
e.printStackTrace();
}
return cloneObj;
}
}
程序执行结果:
bird name:小鸟
bird child name:小小鸟
bird name 2:黄雀
bird child name 2:小黄雀
深克隆实现方式二:所有引用类型都实现克隆
学生类:
public class Student implements Cloneable {
private String name;
private int age;
public Student(String name, int age){
this.name = name;
this.age = age;
}
@Override
protected Object clone() {
try {
return super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
return null;
}
}
...省略Student 的set get方法
}
老师类:
public class Teacher implements Cloneable{
private String name;
private int age;
private Student student;
public Teacher(String name, int age, Student student){
this.name = name;
this.age = age;
this.student = student;
}
...省略Teacher类get set方法
// 覆盖
@Override
public Object clone() {
Teacher t = null;
try {
t = (Teacher) super.clone();
t.student = (Student)student.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return t;
}
}
测试:
public class test {
public static void main(String[] args) {
Student s = new Student("学生1", 11);
Teacher origin = new Teacher("老师原对象", 23, s);
System.out.println("克隆前的学生姓名:" + origin.getStudent().getName());
Teacher clone = (Teacher) origin.clone();//调用克隆方法
// 更改克隆后的学生信息 更改了姓名
clone.getStudent().setName("我是克隆对象更改后的学生2");
System.out.println("克隆后的学生姓名:" + clone.getStudent().getName());
}
}
运行结果:
克隆前的学生姓名:学生1
克隆后的学生姓名:我是克隆对象更改后的学生2
总结
- 浅克隆:只会复制对象的值类型,而不会复制对象的引用类型;
- 深克隆:复制整个对象,包含值类型和引用类型。
- 克隆可分为浅克隆和深克隆,实际应用中一般使用深克隆
- 深克隆有两种实现方法:
- 使用序列化(推荐)
- 所有引用类型都实现克隆
- 使用clone()方法需要实现Cloneable接口,否则将抛出CloneNotSupportedException异常.
- 原型模式就是使用克隆,快捷创建对象.
- Object中clone()方法是一个native 方法,也就是原生函数,本地方法,使用操作系统底层的语言实现的,因此执行效率更高
扩展
Cloneable接口和Serializable接口的代码非常简单,它们都是空接口,这种空接口也称为标识接口,标识接口中没有任何方法的定义,其作用是告诉JRE这些接口的实现类是否具有某个功能,如是否支持克隆、是否支持序列化等。
还有什么问题小伙伴们可以留言讨论