带你详细了解clone方法

说到clone,工作中可能使用到的不多,所以今天就给大家讲一下。

clone方法是Object类的一个方法可能大家都知道,但是可能很多人都没有注意到clone方法的修饰符,我们来看一下源码

public class Object {

     * @throws  CloneNotSupportedException  if the object's class does not
     *               support the {@code Cloneable} interface. Subclasses
     *               that override the {@code clone} method can also
     *               throw this exception to indicate that an instance cannot
     *               be cloned.
    protected native Object clone() throws CloneNotSupportedException;
}

得出以下三个结论:
- clone是一个native方法,而不是java方法,所以可以通过这个修饰符得出结论,clone方法并不会像java一样new一个新对象出来,而是创建一个新的对象,将原对象的数据复制了一份
- clone方法的修饰符是protected,所以任意对象想要调用clone方法,必须自己实现clone方法
- clone方法会抛出CloneNotSupportedException异常。通过注释,我们可以知道只有实现了Cloneable接口的类才能调用clone方法,否则会抛出CloneNotSupportedException异常

接下来我们详细讲一下clone方法的复制过程
我们先来看一下下面这个测试用例:

public class MyTest {
    public static void main(String[] args) throws CloneNotSupportedException {
        User user = new User(new Hobby());
        User user2 = (User)user.clone();
        System.out.println(user == user2);
        System.out.println(user.getHobby() == user2.getHobby());
    }
}

class User implements Cloneable{

    private Hobby hobby;

    public User(Hobby hobby) {
        super();
        this.hobby = hobby;
    }

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

    public Hobby getHobby() {
        return hobby;
    }

}

class Hobby{

    public Hobby() {
        super();
    }

}
  • 因为user != user2,所以我们可以知道,clone方法并不是创建一个新的变量user2,用于指向原对象user的内存地址,而是开辟了一块新的内存,创建了一个新的对象
  • 因为user.getHobby() == user2.getHobby(),所以我们可以知道,Object类的clone方法对于复制User类的hobby属性,没有new一个新的对象,而是直接引用了原对象hobby属性的地址

得出结论,Object类的clone方法,对于成员变量引用的其他对象不会创建一个新的对象,只是引用原对象的地址,术语上叫浅层复制。而如果我们想将引用对象也复制一份,而不是单纯的引用地址,就需要深层复制

那么怎样去实现深层复制呢?可以去重写User的clone方法,使得在复制User对象的时候也将Hobby对象复制一份

class User implements Cloneable{

    private Hobby hobby;

    public User(Hobby hobby) {
        super();
        this.hobby = hobby;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        User user = (User)super.clone();
        user.hobby = (Hobby)hobby.clone();
        return user;
    }

    public Hobby getHobby() {
        return hobby;
    }

}

class Hobby implements Cloneable{

    public Hobby() {
        super();
    }

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

}

我们在调用User的clone方法的时候,会先去调用Hobby的clone方法,这样就能保证复制出来的User对象的Hobby属性是也是一个新的的对象。

看起来重写了User的clone方法就实现了我们想要的深层复制,但是如果Hobby类的成员变量也有引用,引用一个Music对象怎么办呢?似乎只能再重写Hobby的clone方法。

这样就陷入一个无限循环,所以clone方法理论上是不存在绝对的深层复制的。

那么如果说,我就想实现一个绝对的深层复制,怎么办呢?

有时候思路不能局限于一个方法,如果想从clone方法上找解决方案,多半是不现实的了。

开阔一下思维就能想到可以使用序列化来实现深层复制,序列化和反序列化生成的对象以及对象的属性一定都是新的对象,而不是对原对象地址的引用,下面给出针对上面User类的复制代码(类必须实现Serializable接口)

class User implements Serializable {

    private static final long serialVersionUID = 1L;

    private Hobby hobby;

    public Object deepClone() throws IOException, OptionalDataException, ClassNotFoundException {// 将对象写到流里
        ByteArrayOutputStream bo = new ByteArrayOutputStream();
        ObjectOutputStream oo = new ObjectOutputStream(bo);
        oo.writeObject(this);
        ByteArrayInputStream bi = new ByteArrayInputStream(bo.toByteArray());
        ObjectInputStream oi = new ObjectInputStream(bi);
        return (oi.readObject());
    }

    public User(Hobby hobby) {
        super();
        this.hobby = hobby;
    }

    public Hobby getHobby() {
        return hobby;
    }

}

class Hobby implements Serializable {

    private static final long serialVersionUID = 1L;

    public Hobby() {
        super();
    }

}

关于clone方法、浅层复制以及深层复制的分享到这里就结束了,希望对大家有所帮助。

扫码关注,不迷路
扫码关注

猜你喜欢

转载自blog.csdn.net/Leon_cx/article/details/81091111