前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >聊一下深/浅克隆的那些要点

聊一下深/浅克隆的那些要点

作者头像
东边的大西瓜
修改2022-07-01 19:34:50
3270
修改2022-07-01 19:34:50
举报
文章被收录于专栏:吃着西瓜学Java吃着西瓜学Java

聊一下深/浅克隆的那些要点

什么是克隆?它的作用?

Clone,克隆的意思,即通过克隆来获取与源体一样的新个体。还记得初中生物课本上的多利羊吗?亦或是科幻电影中BOSS们害怕自己死亡,而存放在培养皿中的那些克隆体。所以说在Java中我们可以通过Clone方法来创建一个新的对象,这里让我们来回顾一下创建新对象的几种创建方式:

使用new关键字

调用构造函数

反射

调用构造函数

使用clone方法

没有调用构造函数

使用反序列化

没有调用构造函数

浅克隆

将源对象中为基本数据类型的成员变量的属性都复制给克隆对象,为引用数据类型的成员变量的引用地址复制给克隆对象。

深克隆

深克隆:与浅克隆相比,将引用数据类型所引用的对象也都会复制一份。

如何在Java中去实现Clone

我们通过查询api可以看到我们的Object方法中是有给我们提供clone()方法的,并且其是native的。

在上面的注释的大意就是,一个类要想使用clone()方法必须实现Cloneable接口,否则抛出CloneNotSupportedException异常。

而且通过阅读注解我们将得到对于clone()方法的建议,但并不是必须保证的:

  • 对于所有对象来说,x.clone() !=x 应当返回 true,因为克隆对象与原对象不是同一个对象;
  • 对于所有对象来说,x.clone().getClass() == x.getClass() 应当返回 true,因为克隆对象与原对象的类型是一样的;
  • 对于所有对象来说,x.clone().equals(x) 应当返回 true,因为使用 equals 比较时,它们的值都是相同的。

实现浅克隆的方法:

当我们仅仅通过实现Cloneable并重写调用Object的clone()方法时,我们将实现浅克隆,因为clone()方法是被protected修饰的。

小tips:protected的作用域

当前类

同包

子类

不同包

×

  • 基类的protected成员是包内可见的,并且对子类可见;
  • 若子类与基类不在同一包中,那么在子类中,子类实例可以访问其从基类继承而来的protected方法,而不能访问基类实例的protected方法。

实现深克隆的方法:

  • 所有对象都实现克隆方法;
  • 通过构造方法实现深克隆;
  • 使用 JDK 自带的字节流实现深克隆;

实现浅克隆,代码实战

  • 实现Cloneable接口,并且必须重写clone方法,因为clone方法被protected关键字修饰,只有重写才能调用
代码语言:javascript
复制
    public class CloneTest {
    public static void main(String[] args) throws CloneNotSupportedException {
        A a = new A();
        a.setB_value(1);
        a.setR_value(new int[]{1});
        //第一次打印a属性
        System.out.println("第一次打印a属性:"+a.getB_value()+" "+a.getR_value()[0]);
        A b = (A)a.clone();
        b.setB_value(2);
        int[] c = b.getR_value();
        c[0]=2;
        b.setR_value(c);
        //第一次打印b属性
        System.out.println("第一次打印b属性:"+b.getB_value()+" "+b.getR_value()[0]);
        //第二次打印a属性
        System.out.println("第一次打印a属性:"+a.getB_value()+" "+a.getR_value()[0]);
    }
    static class A implements Cloneable {
        private int B_value;
        //先明确的是数组是对象
        private int[] R_value;
        
        @Override
        protected Object clone() throws CloneNotSupportedException {
            return super.clone();
        }
        /*
          get、set方法
      
         */
    }

}

打印执行结果:

代码语言:javascript
复制
第一次打印a属性:1 1
第一次打印b属性:2 2
第一次打印a属性:1 2

这里我之所以选择数组,是因为数组比较特殊数组本身就是引用类型,而且可以更好的理解我们Arrays.copy(),Arrays类提供的copy()方法就是一个现成的浅拷贝方法。

实现深克隆,代码实战

先创建Blog和Author两个类,这里有个小知识点,注意查看:

代码语言:javascript
复制
/**
 *博客类
 */
public class Blog {
    //标题和作者
    private String title; //不可变对象
    private Author author;//引用对象
}

/**
 *作者类
 */
public class Author {
    //昵称和账号
    private String nickname;
    private String username;
}

注意这里我特意使用了String,有些同学可能有疑问了这不也是一个对象类型吗?没错,不过它是不可变的,在《Effective Java》(原书第3版)中有这样一句话:“不可变类永远都不应该提供克隆方法,因为它只会激发不必要的克隆。”所以这里的String类型的字段,我们要当成一个“基本数据类型”。

1. 所有对象都实现克隆方法:

代码语言:javascript
复制
public class CloneTest {
    public static void main(String[] args) throws CloneNotSupportedException {
        Author author = new Author();
        author.setNickname("zzxkj");
        author.setUsername("123456");
        //创建源对象
        Blog blog = new Blog();
        blog.setAuthor(author);
        //克隆
        Blog blog1 = (Blog) blog.clone();
        Author author1 = blog1.getAuthor();
        author1.setUsername("000000");
        System.out.println(blog.getAuthor().getUsername()+" "+blog1.getAuthor().getUsername());
    }
    static class Blog implements Cloneable{
        //标题和作者
        private String title;
        private Author author;
        //重写克隆方法
        @Override
        protected Object clone() throws CloneNotSupportedException {
            Blog blog = (Blog) super.clone();
            // 引用类型克隆赋值
            blog.setAuthor((Author) this.author.clone());
            return blog;
        }
  /*
  省略set和get方法
  * */
    }

    static class Author implements Cloneable{
        //昵称和账号
        private String nickname;
        private String username;
        //重写克隆方法
        @Override
        protected Object clone() throws CloneNotSupportedException {
            return super.clone();
        }

  /*
  省略set和get方法
  * */
    }
}

打印执行结果:

代码语言:javascript
复制
123456 000000

2.通过构造方法实现深克隆

《Effective Java》 中推荐使用构造器(Copy Constructor)来实现深克隆,如果构造器的参数为基本数据类型或字符串类型则直接赋值,如果是对象类型,则需要重新 new 一个对象。

代码语言:javascript
复制
public class CloneTest {
    public static void main(String[] args) throws CloneNotSupportedException {
        Author author = new Author();
        author.setNickname("zzxkj");
        author.setUsername("123456");
        //创建源对象
        Blog blog = new Blog();
        blog.setAuthor(author);
        //克隆,其实就是比葫芦画瓢再创建一遍
        Blog blog1 = new Blog();
        Author author1 = new Author();
        author1.setNickname("zzxkj");
        author1.setUsername("000000");
        blog1.setAuthor(author1);
        System.out.println(blog.getAuthor().getUsername()+" "+blog1.getAuthor().getUsername());
    }
    static class Blog{
        //标题和作者
        private String title;
        private Author author;

        @Override
        protected Object clone() throws CloneNotSupportedException {
            Blog blog = (Blog) super.clone();
            blog.setAuthor((Author) this.author.clone());
            return blog;
        }
  /*
  省略set和get方法
  * */
    }

    static class Author{
        //昵称和账号
        private String nickname;
        private String username;

        @Override
        protected Object clone() throws CloneNotSupportedException {
            return super.clone();
        }

  /*
  省略set和get方法
  * */
    }
}

打印执行结果:

代码语言:javascript
复制
123456 000000

3.通过字节流实现深克隆

我们都知道Java中的序列化的概念,那么这时就需要我们每个类实现Serializable接口,先将源对象写入到内存中的字节流,然后再从这个字节流中读出刚刚存储的信息,来作为一个新的对象返回,那么这个新对象和原型对象就不存在任何地址上的共享,这样就实现了深克隆。

代码语言:javascript
复制
public class CloneTest {
    public static void main(String[] args) throws CloneNotSupportedException {
        Author author = new Author();
        author.setNickname("zzxkj");
        author.setUsername("123456");
        //创建源对象
        Blog blog = new Blog();
        blog.setAuthor(author);
        //克隆
        Blog blog1 = (Blog) StreamClone.clone(blog);
        Author author1 = blog1.getAuthor();
        author1.setUsername("000000");
        System.out.println(blog.getAuthor().getUsername()+" "+blog1.getAuthor().getUsername());
    }

    /**
     * 通过字节流实现克隆
     */
    static class StreamClone {
        public static <T extends Serializable> T clone(People 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;
        }
    }
    static class Blog implements Serializable{
        //标题和作者
        private String title;
        private Author author;
  /*
  省略set和get方法
  * */
    }

    static class Author implements Serializable{
        //昵称和账号
        private String nickname;
        private String username;
  /*
  省略set和get方法
  * */
    }
}

打印执行结果:

代码语言:javascript
复制
123456 000000
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-04-18,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 吃着西瓜学Java 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 聊一下深/浅克隆的那些要点
    • 什么是克隆?它的作用?
      • 浅克隆
        • 深克隆
          • 如何在Java中去实现Clone
            • 实现浅克隆的方法:
            • 实现深克隆的方法:
          • 实现浅克隆,代码实战
            • 实现深克隆,代码实战
              • 1. 所有对象都实现克隆方法:
              • 2.通过构造方法实现深克隆
              • 3.通过字节流实现深克隆
          相关产品与服务
          文件存储
          文件存储(Cloud File Storage,CFS)为您提供安全可靠、可扩展的共享文件存储服务。文件存储可与腾讯云服务器、容器服务、批量计算等服务搭配使用,为多个计算节点提供容量和性能可弹性扩展的高性能共享存储。腾讯云文件存储的管理界面简单、易使用,可实现对现有应用的无缝集成;按实际用量付费,为您节约成本,简化 IT 运维工作。
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档