首页 > 其他分享 >影子clone,深度clone

影子clone,深度clone

时间:2023-05-17 19:35:03浏览次数:35  
标签:aInt 影子 clone b1 b2 深度 c2 unCA


下面的例子包含三个类UnCloneA,CloneB,CloneMain。CloneB类包含了一个UnCloneA的实例和一个int类型变量,并且重载clone()方法。CloneMain类初始化UnCloneA类的一个实例b1,然后调用clone()方法生成了一个b1的拷贝b2。最后考察一下b1和b2的输出:

 

package clone;

class UnCloneA {

    private int i;

    public UnCloneA(int ii) { i = ii; }

    public void doubleValue() { i *= 2; }

    public String toString() {

        return Integer.toString(i);

    }

}

class CloneB implements Cloneable{

    public int aInt;

    public UnCloneA unCA = new UnCloneA(111);

    public Object clone(){

        CloneB o = null;

        try{

            o = (CloneB)super.clone();

        }catch(CloneNotSupportedException e){

            e.printStackTrace();

        }

        return o;

    }

}

public class CloneMain {

    public static void main(String[] a){

        CloneB b1 = new CloneB();

        b1.aInt = 11;

        System.out.println("before clone,b1.aInt = "+ b1.aInt);

        System.out.println("before clone,b1.unCA = "+ b1.unCA);

               

        CloneB b2 = (CloneB)b1.clone();

        b2.aInt = 22;

        b2.unCA.doubleValue();

        System.out.println("=================================");

        System.out.println("after clone,b1.aInt = "+ b1.aInt);

        System.out.println("after clone,b1.unCA = "+ b1.unCA);

        System.out.println("=================================");

        System.out.println("after clone,b2.aInt = "+ b2.aInt);

        System.out.println("after clone,b2.unCA = "+ b2.unCA);

    }

}

/** RUN RESULT:

before clone,b1.aInt = 11

before clone,b1.unCA = 111

=================================

after clone,b1.aInt = 11

after clone,b1.unCA = 222

=================================

after clone,b2.aInt = 22

after clone,b2.unCA = 222

*/

 

输出的结果说明int类型的变量aInt和UnCloneA的实例对象unCA的clone结果不一致,int类型是真正的被clone了,因为改变了b2中的aInt变量,对b1的aInt没有产生影响,也就是说,b2.aInt与b1.aInt已经占据了不同的内存空间,b2.aInt是b1.aInt的一个真正拷贝。相反,对b2.unCA的改变同时改变了b1.unCA,很明显,b2.unCA和b1.unCA是仅仅指向同一个对象的不同引用!从中可以看出,调用Object类中clone()方法产生的效果是:先在内存中开辟一块和原始对象一样的空间,然后原样拷贝原始对象中的内容。对基本数据类型,这样的操作是没有问题的,但对非基本类型变量,我们知道它们保存的仅仅是对象的引用,这也导致clone后的非基本类型变量和原始对象中相应的变量指向的是同一个对象。

 

大多时候,这种clone的结果往往不是我们所希望的结果,这种clone也被称为"影子clone"。要想让b2.unCA指向与b2.unCA不同的对象,而且b2.unCA中还要包含b1.unCA中的信息作为初始信息,就要实现深度clone。

 

l     怎么进行深度clone?

把上面的例子改成深度clone很简单,需要两个改变:一是让UnCloneA类也实现和CloneB类一样的clone功能(实现Cloneable接口,重载clone()方法)。二是在CloneB的clone()方法中加入一句o.unCA = (UnCloneA)unCA.clone();

程序如下:

package clone.ext;

class UnCloneA implements Cloneable{

    private int i;

    public UnCloneA(int ii) { i = ii; }

    public void doubleValue() { i *= 2; }

    public String toString() {

        return Integer.toString(i);

    }

    public Object clone(){

        UnCloneA o = null;

        try{

            o = (UnCloneA)super.clone();

        }catch(CloneNotSupportedException e){

            e.printStackTrace();

        }

        return o;

    }

}

class CloneB implements Cloneable{

    public int aInt;

    public UnCloneA unCA = new UnCloneA(111);

    public Object clone(){

        CloneB o = null;

        try{

            o = (CloneB)super.clone();

        }catch(CloneNotSupportedException e){

            e.printStackTrace();

        }

        o.unCA = (UnCloneA)unCA.clone();

        return o;

    }

}

public class CloneMain {

    public static void main(String[] a){

        CloneB b1 = new CloneB();

        b1.aInt = 11;

        System.out.println("before clone,b1.aInt = "+ b1.aInt);

        System.out.println("before clone,b1.unCA = "+ b1.unCA);

               

        CloneB b2 = (CloneB)b1.clone();

        b2.aInt = 22;

        b2.unCA.doubleValue();

        System.out.println("=================================");

        System.out.println("after clone,b1.aInt = "+ b1.aInt);

        System.out.println("after clone,b1.unCA = "+ b1.unCA);

        System.out.println("=================================");

        System.out.println("after clone,b2.aInt = "+ b2.aInt);

        System.out.println("after clone,b2.unCA = "+ b2.unCA);

    }

}

/** RUN RESULT:

before clone,b1.aInt = 11

before clone,b1.unCA = 111

=================================

after clone,b1.aInt = 11

after clone,b1.unCA = 111

=================================

after clone,b2.aInt = 22

after clone,b2.unCA = 222

*/

 

可以看出,现在b2.unCA的改变对b1.unCA没有产生影响。此时b1.unCA与b2.unCA指向了两个不同的UnCloneA实例,而且在CloneB b2 = (CloneB)b1.clone();调用的那一刻b1和b2拥有相同的值,在这里,b1.i = b2.i = 11。

 

要知道不是所有的类都能实现深度clone的。例如,如果把上面的CloneB类中的UnCloneA类型变量改成StringBuffer类型,看一下JDK API中关于StringBuffer的说明,StringBuffer没有重载clone()方法,更为严重的是StringBuffer还是一个final类,这也是说我们也不能用继承的办法间接实现StringBuffer的clone。如果一个类中包含有StringBuffer类型对象或和StringBuffer相似类的对象,我们有两种选择:要么只能实现影子clone,要么就在类的clone()方法中加一句(假设是SringBuffer对象,而且变量名仍是unCA): o.unCA = new StringBuffer(unCA.toString()); //原来的是:o.unCA = (UnCloneA)unCA.clone();

 

还要知道的是除了基本数据类型能自动实现深度clone以外,String对象是一个例外,它clone后的表现好象也实现了深度clone,虽然这只是一个假象,但却大大方便了我们的编程。

 

l     Clone中String和StringBuffer的区别

应该说明的是,这里不是着重说明String和StringBuffer的区别,但从这个例子里也能看出String类的一些与众不同的地方。

 

下面的例子中包括两个类,CloneC类包含一个String类型变量和一个StringBuffer类型变量,并且实现了clone()方法。在StrClone类中声明了CloneC类型变量c1,然后调用c1的clone()方法生成c1的拷贝c2,在对c2中的String和StringBuffer类型变量用相应的方法改动之后打印结果:

package clone;

class CloneC implements Cloneable{

    public String str;

    public StringBuffer strBuff;

    public Object clone(){

        CloneC o = null;

        try{

            o = (CloneC)super.clone();

        }catch(CloneNotSupportedException e){

            e.printStackTrace();

        }

        return o;

    }

   

}

public class StrClone {

    public static void main(String[] a){

        CloneC c1 = new CloneC();

        c1.str = new String("initializeStr");

        c1.strBuff = new StringBuffer("initializeStrBuff");

        System.out.println("before clone,c1.str = "+ c1.str);

        System.out.println("before clone,c1.strBuff = "+ c1.strBuff);

               

        CloneC c2 = (CloneC)c1.clone();

        c2.str = c2.str.substring(0,5);

        c2.strBuff = c2.strBuff.append(" change strBuff clone");

        System.out.println("=================================");

        System.out.println("after clone,c1.str = "+ c1.str);

        System.out.println("after clone,c1.strBuff = "+ c1.strBuff);

        System.out.println("=================================");

        System.out.println("after clone,c2.str = "+ c2.str);

        System.out.println("after clone,c2.strBuff = "+ c2.strBuff);

    }

}

/* RUN RESULT

before clone,c1.str = initializeStr

before clone,c1.strBuff = initializeStrBuff

=================================

after clone,c1.str = initializeStr

after clone,c1.strBuff = initializeStrBuff change strBuff clone

=================================

after clone,c2.str = initi

after clone,c2.strBuff = initializeStrBuff change strBuff clone

*

*/

 

打印的结果可以看出,String类型的变量好象已经实现了深度clone,因为对c2.str的改动并没有影响到c1.str!难道Java把Sring类看成了基本数据类型?其实不然,这里有一个小小的把戏,秘密就在于c2.str = c2.str.substring(0,5)这一语句!实质上,在clone的时候c1.str与c2.str仍然是引用,而且都指向了同一个String对象。但在执行c2.str = c2.str.substring(0,5)的时候,它作用相当于生成了一个新的String类型,然后又赋回给c2.str。这是因为String被Sun公司的工程师写成了一个不可更改的类(immutable class),在所有String类中的函数都不能更改自身的值。下面给出很简单的一个例子:

 

package clone; 
public class StrTest { public static void main(String[] args) {
String str1 = "This is a test for immutable"; 
String str2 = str1.substring(0,8); 
System.out.println("print str1 : " + str1); 
System.out.println("print str2 : " + str2); }
 } /* RUN RESULT print str1 : This is a test for immutable print str2 : This is */

 

例子中,虽然str1调用了substring()方法,但str1的值并没有改变。类似的,String类中的其它方法也是如此。当然如果我们把最上面的例子中的这两条语句

 

c2.str = c2.str.substring(0,5);

c2.strBuff = c2.strBuff.append(" change strBuff clone");


      

 

改成下面这样:

c2.str.substring(0,5);

c2.strBuff.append(" change strBuff clone");

 

去掉了重新赋值的过程,c2.str也就不能有变化了,我们的把戏也就露馅了。但在编程过程中只调用

c2.str.substring(0,5);

 

语句是没有任何意义的。

应该知道的是在Java中所有的基本数据类型都有一个相对应的类,象Integer类对应int类型,Double类对应double类型等等,这些类也与String类相同,都是不可以改变的类。也就是说,这些的类中的所有方法都是不能改变其自身的值的。这也让我们在编clone类的时候有了一个更多的选择。同时我们也可以把自己的类编成不可更改的类。

标签:aInt,影子,clone,b1,b2,深度,c2,unCA
From: https://blog.51cto.com/u_15012132/6293326

相关文章

  • 动手学深度学习(三) 多层感知机
    多层感知机多层感知机的基本知识使用多层感知机图像分类的从零开始的实现使用pytorch的简洁实现多层感知机的基本知识深度学习主要关注多层模型。在这里,我们将以多层感知机(multilayerperceptron,MLP)为例,介绍多层神经网络的概念。隐藏层下图展示了一个多层感知机的神经网络图,它含有......
  • 动手学深度学习(十二) NLP循环神经网络进阶
    GRURNN存在的问题:梯度较容易出现衰减或爆炸(BPTT)⻔控循环神经⽹络:捕捉时间序列中时间步距离较⼤的依赖关系RNN:ImageNameGRU:ImageName•重置⻔有助于捕捉时间序列⾥短期的依赖关系;•更新⻔有助于捕捉时间序列⾥⻓期的依赖关系。载入数据集importos......
  • leetcode:二叉树的最大深度
    题目描述给定一个二叉树,找出其最大深度。二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。说明: 叶子节点是指没有子节点的节点。示例:给定二叉树[3,9,20,null,null,15,7],3/\920/\157返回它的最大深度 3。题目链接:104.二叉树......
  • 动手学深度学习(一) 线性回归
    线性回归主要内容包括:线性回归的基本要素线性回归模型从零开始的实现线性回归模型使用pytorch的简洁实现线性回归的基本要素模型为了简单起见,这里我们假设价格只取决于房屋状况的两个因素,即面积(平方米)和房龄(年)。接下来我们希望探索价格与这两个因素的具体关系。线性回归假设输出与......
  • 动手学深度学习(十) NLP 语言模型与数据集
    语言模型一段自然语言文本可以看作是一个离散时间序列,给定一个长度为的词的序列,语言模型的目标就是评估该序列是否合理,即计算该序列的概率:本节我们介绍基于统计的语言模型,主要是元语法(-gram)。在后续内容中,我们将会介绍基于神经网络的语言模型。语言模型假设序列中的每个词是依次生......
  • 浅谈Javascript 中几种克隆(clone)方式
    一:在Javascript里,如果克隆对象是基本类型,我们直接赋值就可以了:Js代码varsStr="kingwell";varcStr=sStr;alert(cStr);//输出kingwellsStr="abc";alert(cStr);//输出kingwell; 把一个值赋给另一个变量时,当那个变量的值改变的时候,另一个值不会受到影响。 ......
  • 基于LSTM-RNN的深度学习网络的训练对比matlab仿真
    1.算法仿真效果matlab2022a仿真结果如下:   2.算法涉及理论知识概要        长短期记忆网络(LSTM,LongShort-TermMemory)是一种时间循环神经网络,是为了解决一般的RNN(循环神经网络)存在的长期依赖问题而专门设计出来的,所有的RNN都具有一种重复神经网络模块的链式形......
  • KDDCup深度学习
    importpandasaspdimporttorchimporttorchvisionimporttorch.nnasnnimportnumpyasnpimporttorch.utils.dataasDatafromsklearnimportpreprocessingimportmatplotlib.pyplotaspltepochs=20batch_size=64lr=0.001#我直接将官网的格式改成了c......
  • 【C++深度解析】9、const 常量?只读变量?
    文章目录1const常量的判别准则1.1编程实验2小结看了前面的关于const的内容,不知道是不是有疑问,const什么时候为只读变量,什么时候是常量?1const常量的判别准则只有用字面量初始化的const常量才会进入符号表使用其他变量初始化的const常量仍然是只读变量被volatile修......
  • 作为一个 Android 开发者,我为什么要在意深度学习?
    阅读本文大概需要5.20分钟。AlphaGo再次战胜人类,Google发布TensorFlow正式版,百度筹建深度学习实验室......人人都在谈论机器学习、深度学习,作为移动应用开发者,这些概念离你很远吗?其实不然,如果你不想被未来淘汰,想获得更好的工作,想在技术上变得更牛掰,机器学习和深度学习可能是......