NO.3 厉害了,clone哥 | Java敲黑板系列

开场白

老铁 :今天我们“Java敲黑板系列”迎来了一位特殊嘉宾:他是Java家族顶级boss——Object的“左右护法”之一的“右护法” ——clone方法。他为什么会有这样的名望?江湖上给了他一个称号——“造物主”。那么“左护法”是谁了?——new方法。也就是说,所有的Java对象都是由“new方法”与“clone方法”所产生的;因此,他们是Java世界里面名副其实的“造物主”。

clone方法 :大家好,我是clone方法,很高心能来到“Java敲黑板系列”演播室和大家见面。

从一个例子讲起

//包包的颜色类
class Color{
    private String name; //包包的颜色名称
    public Color(String name){
        this.name = name;
    }
    public String getName() {
        return name;
    }
    public void setName(String name){
        this.name = name;
    }
}

//包包类
public class Package implements Cloneable{
    private String name;  //包包的名字
    private Color  color; //包包的颜色

    public Package(String name,Color color){
        this.name = name;
        this.color = color;
    }

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }

    public Color getColor() {
        return color;
    }
    public void setColor(Color color) {
        this.color = color;
    }

    public Package clone(){
        Package pkg = null;
        try {
            pkg = (Package)super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return pkg;
    }

    //输出包包的信息
    public void print(){
        System.out.println("Package "+getName()+" => Color is : "+getColor().getName());
    }

    //测试客户端
    public static void main(String[] args) {
        Package pkg1 = new Package("Dior",new Color("Red"));
        pkg1.print();//打印Dior包的信息

        //利用clone方法创建一个新对象,并更新新对象相关属性
        Package pkg2 = pkg1.clone();
        pkg2.setName("0007");
        pkg2.getColor().setName("White");

        pkg1.print();//再次打印Dior包的信息
        pkg2.print();//打印0007包的信息
    }
}

读到这里,老铁们都可以试着想想程序的输出信息。结果是让人大跌眼镜的:

    Package Dior => Color is : Red
    Package Dior => Color is : White
    Package 0007 => Color is : White

对pkg1进行clone后,生成了新对象pkg2;接着对pkg2的名称与颜色等属性进行了修改。但是,让人意外的是:明明是只想修改pkg2的颜色属性,但是结果是把pkg1的颜色也修改了,并且还与pkg2颜色一模一样。这样的“撞衫”事件发生,让人多丢面子,这会让人觉得我一点品味都没有,买个包都和别人一模一样。

元芳,你怎么看?

遇到这样的问题,clone实在是很困惑,于是他把这个事情给他的老大Object报告了一下。

Object语重心长的对clone说:“所有的类都继承于我(Object),我提供了一个默认的对象拷贝实现,代码中的super.clone()就是在调用我的默认实现。但是,该方法并非十全十美,它只是提供了一个浅拷贝的方法,即该方法并不会把对象中的所有属性全部真正的clone一份,有一部分clone出来的属性与原来属性保持独立变化,而有一部分则不会。具体的处理规则如下:”,Object清了清嗓子,继续说道。

敲黑板:

  • 如果属性是基本数据类型,Java基本类型包括了浮点型(float、double);整型(byte、short、int、long);字符型(char);布尔型(boolean)。那么就直接拷贝其值。
  • 如果属性是对象类型,则拷贝地址引用,也就是说新拷贝的属性对象与原属性对象指向了同一块内存区域,其实是同一对象。
  • 如果属性是String对象类型,该对象比较特殊,clone的也是一个地址引用;但是如果一旦修改该字符串的值,clone出来的对象就会重新生成一个新对象,原有的字符串对象保持不变。从实际的结果这一点来看,可以认为String类型与基本类型是一致的。

解决之道

知道原因以后,其实解决方法也就很简单了,那就是把“浅拷贝”转换为“深拷贝”,如下图所示。

图1 浅拷贝与深拷贝的区别

public Package clone(){
    Package pkg = null;
    try {
        pkg = (Package)super.clone();
        //新增:新生成一个对象,切断与原对象关联,实现深拷贝
        pkg.setColor(new Color(pkg.getColor().getName()));
    } catch (CloneNotSupportedException e) {
        e.printStackTrace();
    }
    return pkg;
}

只需要对代码片段1中的clone方法进行修改(本例中只需要增加一句即可)就能实现Package类对象的深拷贝。通过深拷贝后,就能保证clone出来的对象与原对象互不影响。浅拷贝只是Java提供的一种简单拷贝机制,一般不能直接拿来就用。

思考题

请大家回顾一下“Java敲黑板系列”的NO.1 - NO.3, 内部有一条主线贯穿始终,欢迎大家把自己的感悟写在留言区进行讨论。

小结

敲黑板,画重点:

  • Java提供的默认的clone方法实现了浅拷贝,其特性如下:如果属性是基本数值类型或String对象类型,则会拷贝其值;如果是对象类型,则只是拷贝对象的地址,实在是同一对象,会出现很多隐患。
  • Java默认的clone方法只实现了简单的浅拷贝,在真实的业务应用中会存在很多的安全隐患,一般需要自定义实现深拷贝。
  • clone方法和new方法是Java世界里生成对象的两种方法。但是,从性能上来看,一般用new方法生成对象的效率会比clone高。clone方法主要应用于构造函数比较复杂、成员属性比较繁多或者是用new方法创建对象比较耗时(可采用性能工具测试比较)的业务场景。

彩蛋

NO.2 equals与==的三生三世 | Java敲黑板系列中留了一个思考题,其解答答案如下:

String str1 = "敲黑板";
String str2 = "敲黑板";
String str3 = new String("敲黑板");
boolean  ret;

ret = (str1 == str2);       
//true:str1与str2均为直接量。对于直接量的字符串,Java从性能上有优化机制:
//Java为了避免在一个系统中产生大量的String对象;
//设计了一个字符串常量池容纳String对象。创建一个字符串
//时,首先检查池中是否有字符串内容相等的字符串;如果有则
//直接返回池中该对象的引用;否则创建并放入池中.

ret = (str1.equals(str2));  
//true:String类的equals方法用于检查字符串内容相等

ret = (str1 == str3);       
//false:直接量和对象之间的关系与两个直接量之间的关系不同

ret = (str1.equals(str3));  
//true:String类的equals方法用于检查字符串内容相等

ret = (str1 == str1.trim());
//true:调用trim方法的字符串对象,返回值仍然为该对象的引用

转载自公众号:代码荣耀
图2

猜你喜欢

转载自blog.csdn.net/maijia0754/article/details/80567038