> 文档中心 > 刨析Object中的clone方法,涉及浅拷贝,深拷贝,原型模式

刨析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对象地址不相同falsetrue    //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

总结

  • 浅克隆:只会复制对象的值类型,而不会复制对象的引用类型;
  • 深克隆:复制整个对象,包含值类型和引用类型。
  1. 克隆可分为浅克隆和深克隆,实际应用中一般使用深克隆
  2. 深克隆有两种实现方法:
    • 使用序列化(推荐)
    • 所有引用类型都实现克隆
  3. 使用clone()方法需要实现Cloneable接口,否则将抛出CloneNotSupportedException异常.
  4. 原型模式就是使用克隆,快捷创建对象.
  5. Object中clone()方法是一个native 方法,也就是原生函数,本地方法,使用操作系统底层的语言实现的,因此执行效率更高

扩展

Cloneable接口和Serializable接口的代码非常简单,它们都是空接口,这种空接口也称为标识接口,标识接口中没有任何方法的定义,其作用是告诉JRE这些接口的实现类是否具有某个功能,如是否支持克隆、是否支持序列化等。

还有什么问题小伙伴们可以留言讨论