关于Cloneable接口和clone方法「建议收藏」

关于Cloneable接口和clone方法「建议收藏」1、使用创建对象有两种方式:new和clone当一个对象创建过程复杂,我们是否可以根据已有的对象直接来克隆一份,而不必关系创建的细节呢(原型模式)。1.1JavaObject根类默认提

大家好,又见面了,我是你们的朋友全栈君。

1、使用


 

创建对象有两种方式: new 和 clone

当一个对象创建过程复杂,我们是否可以根据已有的对象直接来克隆一份,而不必关系创建的细节呢(原型模式)。

1.1 Java Object根类默认提供了clone方法:

protected native Object clone() throws CloneNotSupportedException;

一个本地方法,protected权限: 这样做是为避免我们创建每一个类都默认具有克隆能力  

 

1.2 实现Cloneable接口

我们要使用一个对象的clone方法,必须Cloneable接口,这个接口没有任何实现,跟 Serializable一样是一种标志性接口

如果不实现Cloneable接口,会抛出CloneNotSupportedException异常

重写clone方法,使用public修饰(否则外部调用不到),调用父类的clone方法

如下:

public class CloneModel implements  Cloneable{
    private String name;

    private int age;

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

    @Override
    public String toString() {
        return "CloneModel{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

 

1.3、Object.clone() 与 构造方法

我们看一个例子:

CloneModel类:

public class CloneModel implements  Cloneable{
    private String name;

    private int age;

    public CloneModel(){
        System.out.println("will new a instance");
    }

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

    @Override
    public String toString() {
        return "CloneModel{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

当我们调用此对象的clone方法时,构造方法并没有被调用,所以我说创建一个对象new和clone是两条路

public static void main(String[] args) throws CloneNotSupportedException {
        CloneModel cloneModel = new CloneModel();
        System.out.println(cloneModel.clone());
    }

打印:

CloneModel{name=’null’, age=0}

 

2、重写clone方法原则


 

x.clone != x将会是true;

x.clone().getClass()==x.getClass()将会是true(不是绝对的,但建议这么做)

x.clone().equals(x)将会是true(不是绝对的,但建议这么做)

 

3、浅克隆和深克隆


 

3.1 默认clone方法时浅克隆

Object默认的clone方法实际是对域的简单拷贝,对于简单数据类型,是值的拷贝;

对于复杂类型的字段,则是指针地址的拷贝,clone后的对象和原对象指向的还是一个地址空间

所以说默认的clone方法时浅克隆。

例子:

关于Cloneable接口和clone方法「建议收藏」
关于Cloneable接口和clone方法「建议收藏」

class Model2{
    int height;
}

public class CloneModel implements  Cloneable{
    private String name;

    private int age;

    private Model2 model2;

    public CloneModel() {
        this.model2 = new Model2();
    }

    public Model2 getModel2() {
        return model2;
    }

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

    @Override
    public String toString() {
        return "CloneModel{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

View Code

clone之后比较复杂对象是否相等

public static void main(String[] args) throws CloneNotSupportedException {
        CloneModel cloneModel1 = new CloneModel();
        CloneModel cloneModel2 = (CloneModel)cloneModel1.clone();
        System.out.println(cloneModel1.getModel2()==cloneModel2.getModel2());
    }

 

执行返回:true

 

3.2 如何实现深克隆

还是上面的例子,我们改下代码

关于Cloneable接口和clone方法「建议收藏」
关于Cloneable接口和clone方法「建议收藏」

class Model2 implements Cloneable{
    int height;

    @Override
    public Object clone() throws CloneNotSupportedException {
        System.out.println("clone Model2");
        return super.clone();
    }
}

public class CloneModel implements  Cloneable{
    private String name;

    private int age;

    private Model2 model2;

    public CloneModel() {
        this.model2 = new Model2();
    }

    public Model2 getModel2() {
        return model2;
    }

    public void setModel2(Model2 model2) {
        this.model2 = model2;
    }

    @Override
    public CloneModel clone() throws CloneNotSupportedException {
        CloneModel cloneModelTemp = (CloneModel)super.clone();
        cloneModelTemp.setModel2((Model2)cloneModelTemp.getModel2().clone());
        return cloneModelTemp;
    }

    @Override
    public String toString() {
        return "CloneModel{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

View Code

再次测试下:

public static void main(String[] args) throws CloneNotSupportedException {
        CloneModel cloneModel1 = new CloneModel();
        CloneModel cloneModel2 = cloneModel1.clone();
        System.out.println(cloneModel1.getModel2()==cloneModel2.getModel2());
    }

执行返回:false

这么做就要在super.clone的基础上 继续对非基本类型的对象递归的再次clone.

显然这么方式是繁琐的且不可靠的。

有没有其他的方式呢?有 序列化

 

3.3 序列化实现深度克隆

(1) 使用java自身的序列化转为二进制数 ,再反序列化为对象

上面的例子改造下

关于Cloneable接口和clone方法「建议收藏」
关于Cloneable接口和clone方法「建议收藏」

import java.io.Serializable;

class Model2 implements Serializable {
    int height;
}

public class CloneModel implements Serializable {
    private String name;

    private int age;

    private Model2 model2;

    public CloneModel() {
        this.model2 = new Model2();
    }

    public Model2 getModel2() {
        return model2;
    }

}

View Code

 

测试代码:

关于Cloneable接口和clone方法「建议收藏」
关于Cloneable接口和clone方法「建议收藏」

import com.yangfei.test.CloneModel;

import java.io.*;

public class YfTest {
    public static <T extends Serializable> T deepCloneObject(T object) throws IOException {
        T deepClone = null;
        ObjectInputStream ois = null;
        try(ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(baos);
        )
        {
            oos.writeObject(object);
            ByteArrayInputStream bais = new ByteArrayInputStream(baos
                    .toByteArray());
            ois = new ObjectInputStream(bais);
            deepClone = (T)ois.readObject();
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            if(ois != null){
                ois.close();
            }
        }
        return deepClone;
    }

    public static void main(String[] args) throws IOException {
        CloneModel cloneModel1 = new CloneModel();
        CloneModel cloneModel2 = deepCloneObject(cloneModel1);
        System.out.println(cloneModel1.getModel2()==cloneModel2.getModel2());
    }
}

View Code

测试输出:false

 

(2)其他序列化方式,如对象序列化json字符串再反序列化为对象

 我们使用google的gson来进行序列化,上代码

关于Cloneable接口和clone方法「建议收藏」
关于Cloneable接口和clone方法「建议收藏」

import com.google.gson.Gson;

import java.io.Serializable;

class Model2 implements Serializable {
    int height;
}

public class CloneModel implements Serializable {
    private String name;

    private int age;

    private Model2 model2;

    public CloneModel() {
        this.model2 = new Model2();
    }

    public Model2 getModel2() {
        return model2;
    }

    public CloneModel deepClone() {
        Gson gson = new Gson();
        return gson.fromJson(gson.toJson(this), CloneModel.class);
    }

}

View Code

测试代码:

public static void main(String[] args) throws IOException {
        CloneModel cloneModel1 = new CloneModel();
        CloneModel cloneModel2 = cloneModel1.deepClone();
        System.out.println(cloneModel1.getModel2()==cloneModel2.getModel2());
    }

执行返回:false

 

性能对比测试:

上代码:

public static void main(String[] args) throws IOException {
        CloneModel cloneModel1 = new CloneModel();
        long time1 = System.currentTimeMillis();
        for(int i=0;i<1000;i++){
//            CloneModel cloneModel2 = cloneModel1.deepClone();
            CloneModel cloneModel2 = deepCloneObject(cloneModel1);
        }
        long time2 = System.currentTimeMillis();
        System.out.println((time2-time1)+"ms");
    }

  

循环1000次

Serializable耗时:118ms

json耗时:167ms

对比 Serializable gson
易用性 对象要实现Serializable,依赖的子元素依然要实现此接口,不易扩展 无要求,额外的工具控制,易用使用
性能对比 1000次clone耗时118ms,稍好 1000次clone耗时167ms
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/155458.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • tomcat日志配置——如何查看日志

    tomcat日志配置——如何查看日志Tomcat日志设定1、Tomcat日志概述Tomcat日志信息分为两类:一、是运行中的日志,它主要记录运行的一些信息,尤其是一些异常错误日志信息。二、是访问日志信息,它记录的访问的时间,IP,访问的资料等相关信息。2Tomcat日志配置2.1访问日志的配置默认tomcat不记录访问日志,如下方法可以使tomc…

  • Mycat读写分离的简单实现「建议收藏」

    Mycat读写分离的简单实现「建议收藏」文章目录1、Mycat读写分离的配置1.1、Mycat是什么1.2、Mycat能干什么1.2.1、数据库的读写分离1.2.2、数据库读写分离图解1.2.3、数据库分库分表1.2.3.1、水平拆分(分库)1.2.3.2、垂直拆分(分表)1.3、Mycat的搭建1.3.1、前期准备1.3.2、搭建环境1.3.3、Mycat的安装启动关闭1.3.4、Mycat的配置文件1.3.5、server.xml文件的配置1.3.6、schema.xml文件的配置1.4、测试读写分离1、Mycat读写分离的配置1.1、M

    2022年10月13日
  • 自动化测试理论[通俗易懂]

    自动测试并不会在一开始就减少测试人员的工作量、缩短测试时间。自动化测试只能当做是手工测试的补充。毕竟一个项目引入自动化,需要测试人员熟悉这个工具,而且有很多时候不是一个工具就能解决。其次自动化执行的内容也是需要测试人员去编写和考量的,这个过程需要时间。最后就是对自动测试执行后的结果分析,也是需要人自己来做的。1.一些可以自动执行的测试用例:需要多次执行的测试。相反,只执行一次的测试工作一…

  • mysql opkg源_OpenWrt opkg 在线源默认配置

    mysql opkg源_OpenWrt opkg 在线源默认配置destroot/destram/tmplists_dirext/var/opkg-listsoptionoverlay_root/overlaysrc/gzbarrier_breaker_basehttp://downloads.openwrt.org/barrier_breaker/14.07/x86/generic/packages/basesrc/gzbarrier_…

  • 【小5聊】C#基础之Response.ContentType响应内容类型[通俗易懂]

    【小5聊】C#基础之Response.ContentType响应内容类型[通俗易懂]Response.ContentType1、文本格式:html、xml、txtResponse.ContentType=”text/html”;Response.ContentType=”text/xml”;Response.ContentType=”text/plain”;2、图片格式Response.ContentType=”image/jpg”;…

  • ES6数组新方法[通俗易懂]

    ES6数组新方法[通俗易懂]ES6数组新方法目录ES6数组新方法1.`forEach()`和`map()`2.`filter()`3.`reduce()`4.`some()`5.`every()`6.`Array.from()`7.`Array.of()`8.`copyWithin()`9.`find()`和`findIndex()`10.`fill()`11.`entries()`,`keys()`和`values()`12.`includes()`13.`flat()`,`flatMap()`

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号