JavaSE学习随笔(一) Cloneable接口源码分析与技术细节

JavaSE学习随笔(一) Cloneable接口源码分析与技术细节Cloneable接口是Java开发中常用的一个接口,它的作用是使一个类的实例能够将自身拷贝到另一个新的实例中,注意,这里所说的“拷贝”拷的是对象实例,而不是类的定义,进一步说,拷贝的是一个类的实例中各字段的值。本博文将从Cloneable接口的源码入手,对其技术细节和使用方法进行详细的介绍。

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE稳定放心使用

一、引言

 

       Cloneable接口是Java开发中常用的一个接口, 它的作用是使一个类的实例能够将自身拷贝到另一个新的实例中,注意,这里所说的“拷贝”拷的是对象实例,而不是类的定义,进一步说,拷贝的是一个类的实例中各字段的值。

       在开发过程中,拷贝实例是常见的一种操作,如果一个类中的字段较多,而我们又采用在客户端中逐字段复制的方法进行拷贝操作的话,将不可避免的造成客户端代码繁杂冗长,而且也无法对类中的私有成员进行复制,而如果让需要具备拷贝功能的类实现Cloneable接口,并重写clone()方法,就可以通过调用clone()方法的方式简洁地实现实例拷贝功能。接下来我们将从Cloneable接口的源码入手,对其技术细节和使用方法进行详细的介绍。


二、源码分析


       打开JavaSE源码找到Coloneable接口,其源代码如下。我们会发现一件很有意思的事情,那就是Cloneable接口竟然没有定义任何的接口方法,这是为什么呢?

/*Cloneable接口源代码,JDK1.8*/
public interface Cloneable {
}

       Cloneable接口之所以没有定义任何的接口的原因其实很简单,那就是在Java中,所有类的终极父类已经将clone()方法定义为所有类都应该具有的基本功能,只是将该方法声明为了protected类型。该方法定义了逐字段拷贝实例的操作。它是一个native本地方法,因此没有实现体,而且在拷贝字段时,除了Object类的字段外,其子类的新字段也将被拷贝到新的实例中。

<span style="font-size: 18px;">/*Object类中clone()方法的定义*/
protected native Object clone() throws CloneNotSupportedException;</span>

      看到这里,有同学可能会想,既然Object类中既然已经有了一个定义实例拷贝操作的方法,那为什么还是需要让想具备实力拷贝功能的类实现Cloneable接口呢?其实,Cloneable接口在这里起到了一种标识的作用,表明实现它的类具备了实例拷贝功能,在Cloneable接口的官方javadoc文档中有这样一段话:

       “Invoking Object’s clone method on an instance that does not implement the Cloneable interface results in the exception CloneNotSupportedException being thrown. JDK1.8”

也就是说,如果一个类不实现该接口就直接调用clone()方法的话,即便已将clone()方法重写为public,那还是会抛出“不支持拷贝”异常。因此,要想使一个类具备拷贝实例的功能,那么除了要重写Object类的clone()方法外,还必须要实现Cloneable接口。下面的代码即可以使一个Cloneableclass具备了浅拷贝实例(关于浅拷贝和深拷贝的含义将会在下一节介绍,在这里读者只需要将它看作普通拷贝即可)的功能。

class CloneableClass implements Cloneable {
    
    /*
     * user code....
     */

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

 

三、浅拷贝与深拷贝


1. 浅拷贝

      前面提到了浅拷贝与深拷贝的概念,这是任何一种面向对象的编程语言中都必须要讨论的内容。在java中,对象创建后需要有一个引用变量来指向该对象实际的地址空间,也就是说引用变量与对象实体是两个不同的数据体。在Object类的clone()方法中,对对象字段进行复制时,如果字段是基本数据类型(如int、double等),则会复制字段的值到一个新的变量中,而字段是引用类型,则仅会将引用值复制给新对象中的相应字段中,也就是说,两个字段指向了同一个对象实例。我们用一个例子来说具体明浅拷贝的性质。

      首先我们先定义一个用户自定义的类Test,该类中有一个字符串类型的成员变量,该类将用于验证浅拷贝的性质。

class Test {
    
    public String userData = null;
    
    public Test(String userData) {
        this.userData = userData;
    }// constructor
    
}/*UserClass*/

       接着,我们在定义一个具有拷贝实例功能的类实现Cloneable接口并按照上文所述的方式重写Clone()方法,即将方法属性重写为public,并在重写方法中调用Object类的本地clone()方法。除此之外,该类还定义了四个字段,其中一个为基本数据类型,其余为引用数据类型。

class CloneableClass implements Cloneable {
    
    public Test         data1 = null;
    public double       data2 = 0;
    public String       data3 = null;
    public StringBuffer data4 = null;
        
    public CloneableClass(Test data1, double data2, String data3, StringBuffer data4) {
        this.data1 = data1;
        this.data2 = data2;
        this.data3 = data3;
        this.data4 = data4;
    }// constructor

    /**
     * 用于显示对象中各字段的值
     */
    public void show() {
        System.out.println("data1 = " + data1.userData);
        System.out.println("data2 = " + data2);
        System.out.println("data3 = " + data3);
        System.out.println("data4 = " + data4);     
    }// show
    
    /**
     * 重写clone()方法为public类型,并调用Object的本地clone()方法,实现浅拷贝功能
     */
    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }// clone
 
}/*CloneableClass*/

       接下来是客户端代码,首先创建一个CloneableClass类对象作为初始实例,该实例的各字段值如构造函数所示。然后在调用初始实例的clone()方法创建一个拷贝实例,显示初始实例与拷贝实例各字段的值并判断他们是不是指向了同一个对象实例。

public class CloneableDemo {

    public static void main(String[] args) throws CloneNotSupportedException {
        Test test1 = new Test("original data");
        StringBuffer strBuf = new StringBuffer("origin data");
        
        CloneableClass org = new CloneableClass(test1, 1.0, "original", strBuf);
        CloneableClass copy = null;
        Object objTemp = org.clone();
        if (objTemp instanceof CloneableClass) {
            copy = (CloneableClass)objTemp;
        }// if
        
        System.out.println("copy == original? " + (copy == org));
        System.out.println();
        System.out.println("data of original:");
        org.show();
        System.out.println();
        System.out.println("data of copy:");
        copy.show();

        System.out.println();
        System.out.println("org.data1 == copy.data1? " + (org.data1 == copy.data1));
        System.out.println("org.data2 == copy.data2? " + (org.data2 == copy.data2));
        System.out.println("org.data3 == copy.data3? " + (org.data3 == copy.data3));
        System.out.println("org.data4 == copy.data4? " + (org.data4 == copy.data4));
    }// main

}/*CloneableDemo*/

        执行结果:

JavaSE学习随笔(一) Cloneable接口源码分析与技术细节

       从执行结果上来看copy == org为false说明copy和org是两个不同的对象实例(对于引用类型的比变量,“==”判断的是对象地址是否相同,也就是是不是指向了同一个对象),但是他们字段的值却是相同的。copy和org各字段的“==”判断全为true也说明本地Object本地clone()对实例引用型字段进行的是浅拷贝。

       那么浅拷贝会造成什么样的后果呢?由于浅拷贝仅将字段的引用值复制给了新的字段,但是却并没有创建新的相应对象,也就是说copy和org中的两个字段都指向了同一个对象实例。这样,我们对copy中各字段所指向对象的属性进行了修改,org中的成员对象也会随之改变,客户端代码和运行结果如下所示。

public class CloneableDemo {

    public static void main(String[] args) throws CloneNotSupportedException {
        Test test1 = new Test("original data");
        StringBuffer strBuf = new StringBuffer("origin data");
        
        CloneableClass org = new CloneableClass(test1, 1.0, "original", strBuf);
        CloneableClass copy = null;
        Object objTemp = org.clone();
        if (objTemp instanceof CloneableClass) {
            copy = (CloneableClass)objTemp;
        }// if
        
        // 修改copy中各字段指向对象的属性
        copy.data1.userData = "Copy data";
        copy.data2 = 2.0;
        copy.data3 = "Copy";
        copy.data4.replace(0, copy.data4.length(), "Copy data");

        System.out.println();
        System.out.println("After modify, data of original:");
        org.show();
        System.out.println();
        System.out.println("After modify, data of copy:");
        copy.show();
    }// main

}/*CloneableDemo*/

运行结果:

JavaSE学习随笔(一) Cloneable接口源码分析与技术细节

       从运行结果可以看出,由于CloneableClass的data1和data4字段均为引用型变量,所以经过org浅拷贝出来的copy实例中的data1和data4字段与org中的相应字段实际上指向的是同一个对象,因此,copy对成员对象的修改也就导致了org中相应成员对象的随之改变。需要说明的是,data3虽然也是引用型变量,但由于字符串是常量,所以语句copy.data3 = “Copy”并没有对原字符串进行修改,而是将copy的data3字段指向了一个新的字符串“Copy”。


 2. 深拷贝

       从上文讲的浅拷贝概念与性质中不难退出深拷贝的含义,那就是对于引用型变量,深拷贝会开辟一块新的内存空间,将被复制引用所指向的对象实例的各个属性复制到新的内存空间中,然后将新的引用指向块内存(也就是一个新的实例)。要想实现深拷贝的功能,我们在重写clone()方法的时候,就不能再简单地调用Object的本地clone()方法,而是要对上文中的CloneableClass做如下修改:

class CloneableClass implements Cloneable {
    
    public Test         data1 = null;
    public double       data2 = 0;
    public String       data3 = null;
    public StringBuffer data4 = null;
        
    public CloneableClass(Test data1, double data2, String data3, StringBuffer data4) {
        this.data1 = data1;
        this.data2 = data2;
        this.data3 = data3;
        this.data4 = data4;
    }// constructor

    /**
     * 用于显示对象中各字段的值
     */
    public void show() {
        System.out.println("data1 = " + data1.userData);
        System.out.println("data2 = " + data2);
        System.out.println("data3 = " + data3);
        System.out.println("data4 = " + data4);     
    }// show
    
    /**
     * 重写clone()方法为public类型,对每一个字段都创建一个新的对象,并将原字段指向对象中的属性
     * 复制到新的对象中,从而实现深拷贝功能。
     */
    @Override
    public Object clone() throws CloneNotSupportedException {
        Test         data1 = new Test(this.data1.userData);  
        double       data2 = this.data2;
        String       data3 = new String(this.data3);
        StringBuffer data4 = new StringBuffer(this.data4.toString());
        
        CloneableClass copy = new CloneableClass(data1, data2, data3, data4);
        return copy;
    }// clone
 
}/*CloneableClass*/

客户端代码:

public class CloneableDemo {

    public static void main(String[] args) throws CloneNotSupportedException {
        Test test1 = new Test("original");
        StringBuffer strBuf = new StringBuffer("original");
        
        CloneableClass org = new CloneableClass(test1, 1.0, "original", strBuf);
        CloneableClass copy = null;
        Object objTemp = org.clone();
        if (objTemp instanceof CloneableClass) {
            copy = (CloneableClass)objTemp;
        }// if
        
        System.out.println("Before modify, data of original:");
        org.show();
        System.out.println();
        System.out.println("Before modify, data of copy:");
        copy.show();
        
        // 判断两个对象中的引用型字段是否指向了同一个对象实例。
        System.out.println();
        System.out.println("org.data1 == copy.data1? " + (org.data1 == copy.data1));
        System.out.println("org.data2 == copy.data2? " + (org.data2 == copy.data2));
        System.out.println("org.data3 == copy.data3? " + (org.data3 == copy.data3));
        System.out.println("org.data4 == copy.data4? " + (org.data4 == copy.data4));
        
        // 修改copy中各字段指向对象的属性
        copy.data1.userData = "Copy";
        copy.data2 = 2.0;
        copy.data3 = "Copy";
        copy.data4.replace(0, copy.data4.length(), "Copy");

        System.out.println();
        System.out.println("After modify, data of original:");
        org.show();
        System.out.println();
        System.out.println("After modify, data of copy:");
        copy.show();
    }// main

}/*CloneableDemo*/

运行结果:

JavaSE学习随笔(一) Cloneable接口源码分析与技术细节

       可以看出,从copy和org中相应字段已经指向了不同的实例对象,对copy的修改也不会对org产生影响。






版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

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

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

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

(0)


相关推荐

  • cad文字样式设置方法_cad中标注样式的快捷键

    cad文字样式设置方法_cad中标注样式的快捷键有些CAD新手在进行CAD绘图的过程中,想要修改图纸中CAD文字样式时不知道怎么操作,其实很简单,直接调用CAD文字样式快捷键命令即可。下面和小编一起来了解一下浩辰CAD软件中CAD文字样式快捷键命令

  • java线程dump命令_jdk的dump

    java线程dump命令_jdk的dumpjstack用于打印出给定的java进程ID或corefile或远程调试服务的Java堆栈信息,如果是在64位机器上,需要指定选项”-J-d64″,Windows的jstack使用方式只支持以下的这种方式:jstack[-l][F]pid如果java程序崩溃生成core文件,jstack工具可以用来获得core文件的javastack和nativestack的信息,从而可以轻松地知道ja…

  • xdoj递归数列_递归求数组元素之和

    xdoj递归数列_递归求数组元素之和标题:递归数列类别函数与递归程序类型:代码片段时间限制:2S内存限制10000Kb问题描述一个数列A定义如下A(1)=1,A(2)=1/(1+A(1)),A(3)=1/(1+A(2)),……A(n)=1/(1+A(n-1))。定义一个函数function用来计算数列的第第n项的值,函数声明如下:doublefunction(intn);输入说明:输入为1个正整数n,n<=10。输出说明函数输出数列A第n项的值,…

  • webstorm的永久激活码2021【2021.8最新】

    (webstorm的永久激活码2021)最近有小伙伴私信我,问我这边有没有免费的intellijIdea的激活码,然后我将全栈君台教程分享给他了。激活成功之后他一直表示感谢,哈哈~IntelliJ2021最新激活注册码,破解教程可免费永久激活,亲测有效,下面是详细链接哦~https://javaforall.cn/100143.html…

  • Tomcat安装使用与部署Web项目的三种方法

    Tomcat安装使用与部署Web项目的三种方法今天带来Tomcat的安装教程,也会讲到各种目录下代表的含义,重点是在Tomcat服务器上面部署Web项目的三种方法。以上便是Tomcat从零到部署项目的教程了,觉得写的不错或者对你有帮助的话,三连支持博主吧~……

  • IKAnalyzer2012FF + Lucene4.9 TokenStream contract violation: reset()/close() call missing

    IKAnalyzer2012FF + Lucene4.9 TokenStream contract violation: reset()/close() call missing异常信息如下:

发表回复

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

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