Java面试笔试-010-Java中的clone方法

微信公众号:JavaWeb架构师

描述

1)引用传递与值传递
- 由于指针不仅会给开发人员带来使用上的不便,而且也是造成程序不稳定的根源,为了消除C/C++语言的这些缺点,Java语言取消了指针的概念,但这只是在Java语言中没有明确提供指针的概念与用法,而实质上每个new语句返回的都是一个指针的引用,只不过在大部分情况下开发人员不需要关心如何去操作这个指针而已。由于Java取消了指针的概念,因此开发人员在编程中往往忽略了对象和引用的区别,示例如下。


class Obj {
    private String str = "default value";

    public void setStr(String str) {
        this.str  = str;
    }

    public String toString() {
        return str;
    }
}

public class TestRef {
    private Obj aO = new Obj();
    private int aI = 0;

    public Obj getAO() {
        return aO;
    }

    public int getAI() {
        return aI;
    }

    public void changeObj(Obj inObj) {
        inObj.setStr("change value");
    }

    public void changeInt(int inInt) {
        inInt = 1; 
    }

    public static void main(String[] args) {
        TestRef tr = new TestRef();
        System.out.println("----------------------引用类型--------------------------");
        System.out.println("调用changeObj()前:" + tr.getAO());
        tr.changeObj(tr.getAO());
        System.out.println("调用changeObj()后:" + tr.getAO());

        System.out.println("--------------------基本数据类型--------------------------");
        System.out.println("调用changeInt()前:" + tr.getAI());
        tr.changeInt(tr.getAI());
        System.out.println("调用changeInt()后:" + tr.getAI());

    }
}

结果是:

----------------------引用类型--------------------------
调用changeObj()前:default value
调用changeObj()后:change value
--------------------基本数据类型--------------------------
调用changeInt()前:0
调用changeInt()后:0
  • 上面两个看似类似的方法却有着不同的运行结果,主要原因是Java在处理基本数据类型(例如int、char、double等)时,都是采用按值传递(传递的是输入参数的复制)的方式执行,除此之外的其他类型都是按引用传递(传递的是对象的一个引用)的方式执行。对象除了在函数调用时是引用传递,在使用“=”赋值时也采用引用传递,示例代码如下。
class Obj {
    private int aInt = 0;

    public int getAInt() {
        return aInt;
    }

    public void setAInt(int newInt) {
        aInt = newInt;
    }

    public void changeInt() {
        this.aInt = 1;
    }
}

public class TestRef {
    public static void main(String[] args) {
        Obj o1 = new Obj();
        Obj o2;

        o2 = o1;
        o2.changeInt();

        System.out.println("o1: " + o1.getAInt());
        System.out.println("o2: " + o2.getAInt());

    }
}

结果是:

o1: 1
o2: 1

2)浅复制
- 在实际编程中,经常会遇到从某个已有的对象A创建出另外一个与A具有相同状态的对象B,并且对B的修改不会影响到A的情况,例如,Prototype (原型)模式中,就需要clone一个对象实例。在Java语言中,仅仅通过简单的赋值操作显然无法达到这个目的,而Java提供了一个简单有效的clone()方法来满足这个需求。Java中的所有类默认都继承自Object类,而Object类中提供了一个clone()方法。这个方法的作用是返冋一个Objecl对象的复制。这个复制函数返回的是一个新的对象而不是一个引用。那么怎样使用这个方法呢?以下是使用clone()方法的步骤。
1.实现clone的类首先需要继承Cloneable接口。Cloneable接口实质上是一个标识接口,没有任何接口方法。
2.在类中重写Object类中的clone()方法。
3.在clone方法中调用super. clone()。无论clone类的继承结构是什么,super. clone()都会直接或间接调用java.lang.Object类的clone()方法。
4.把浅复制的引用指向原型对象新的克隆体。

实例如下:

class Obj implements Cloneable {
    private int aInt = 0;

    public int getAInt() {
        return aInt;
    }

    public void setAInt(int newInt) {
        aInt = newInt;
    }

    public void changeInt() {
        this.aInt = 1;
    }

    public Object clone() {
        Object o = null;
        try {
            o = (Obj)super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return o;
    }
}

public class TestRef {
    public static void main(String[] args) {
        Obj o1 = new Obj();

        Obj o2 = (Obj) o1.clone();
        o2.changeInt();

        System.out.println("o1: " + o1.getAInt());
        System.out.println("o2: " + o2.getAInt());

    }
}

运行结果:

o1: 0
o2: 1

3)深复制
- 在C++语言中,当开发人员自定义复制构造函数时,会存在浅复制与深复制之分。Java语言在重载clone()方法时也存在同样的问题,当类中只有一些基本的数据类型时,采用上述方法就可以了,但是当类中包含了一些对象时,就需要用到深复制了,实现方法是在对对象调用clone()方法完成复制后,接着对对象中的非基本类型的属性也调用clone()方法完成深复制,示例如下。

import java.util.Date;

class Obj implements Cloneable {
    private Date brith = new Date();

    public Date getBrith() {
        return brith;
    }

    public void setBrith(Date brith) {
        this.brith = brith;
    }

    public void changeBrith() {
        this.brith.setMonth(4);
    }

    public Object clone() {
        Obj o = null;
        try {
            o = (Obj)super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        // 实现深复制
        o.brith = (Date) this.brith.clone();
        return o;
    }
}

public class TestRef {
    public static void main(String[] args) {
        Obj o1 = new Obj();

        Obj o2 = (Obj) o1.clone();
        o2.changeBrith();

        System.out.println("o1: " + o1.getBrith());
        System.out.println("o2: " + o2.getBrith());

    }
}

结果是:

o1: Sat Mar 03 20:20:22 CST 2018
o2: Thu May 03 20:20:22 CST 2018

4)选择深浅
- 那么在编程时,如何选择使用哪种复制方式呢?首先,检査类有无非基本类型(即对象)的数据成员。若没有,则返冋super. clone()即可;若有,确保类中包含的所有非基本类型的成员变量都实现了深复制。

// 先执行浅复制
Object o = super. clone( );

// 对每一个对象执行以下语句:
o. attr = this. gelAttr( ). clone();

// 最后返回o。
return o;
  • 需要注意的是,clone()方法的保护机制在Object中clone()是被声明为protected的。以User类为例,通过声明为protected,就可以保证只有User类里面才能“克隆” User对象,原理可以参考前面关于public、protected、private的讲解。

5)引申

浅复制和深复制有什么区別?

  • 浅复制(Shallow Clone):
    被复制对象的所有变量都含有与原来对象相同的值,而所有对其他对象的引用仍然指向原来的对象。换言之,浅复制仅仅复制所考虑的对象,而不复制它所引用的对象。

  • 深复制(Deep clone):被复制对象的所有变量都含有与原来对象相同的值,除去那些引用其他对象的变量。那些引用其他对象的变量将指向被复制的新对象,而不再是原有的那些被引用的对象。换言之,深复制把复制的对象所引用的对象都复制了—遍。

微信公众号:JavaWeb架构师


其它

源码下载:

关注下方微信公众号,
回复:
JavaMB.code

完整教程PDF版本下载

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值