为什么要克隆?

为什么要克隆对象?直接通过new一个对象不就好了?
克隆的对象可能包含一些已经修改的属性,而通过new出来的对象的属性都是初始化的值。
今天介绍一下两种不同的克隆方法:浅克隆 和 深克隆。

通过实现Cloneable接口克隆

浅克隆

1、被复制的类需要实现Cloneable接口(该接口是标记接口,没有任何方法)
2、重写clone()方法,访问修饰符设为public

public class CloneTest {

    public static void main(String[] args) {
        // 创建User对象
        User user = new User();
        user.setName("xffjs");
        user.setAddress("小飞博客");
        // 克隆对象
        User cloneUser = user.clone();
        System.out.println(user);
        System.out.println(cloneUser);
    }
}

class User implements Cloneable {
    String name;
    String address;

    public String getName() {
        return name;
    }

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

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", address='" + address + '\'' +
                '}';
    }

    @Override
    public User clone() {
        User user = null;
        try {
            user = (User) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return user;
    }
}
// 输出结果:
User{name='xffjs', address='小飞博客'}
User{name='xffjs', address='小飞博客'}

深克隆

我们把User类里面的Address类型改成类,然后在克隆完成以后再修改Address类里面的addr。

public class CloneTest {

    public static void main(String[] args) {
        // 创建User对象
        User user = new User();
        user.setName("xffjs");
        // 创建Address对象并set给user
        Address address = new Address();
        address.setAddr("小飞博客");
        user.setAddress(address);
        // 克隆对象
        User cloneUser = user.clone();
        // 修改Address
        address.setAddr("小飞");
        System.out.println(user);
        System.out.println(cloneUser);
    }
}

class User implements Cloneable {
    String name;
    Address address;

    public String getName() {
        return name;
    }

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

    public Address getAddress() {
        return address;
    }

    public void setAddress(Address address) {
        this.address = address;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", address=" + address +
                '}';
    }

    @Override
    public User clone() {
        User user = null;
        try {
            user = (User) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return user;
    }
}

class Address implements Cloneable {
    String addr;

    public String getAddr() {
        return addr;
    }

    public void setAddr(String addr) {
        this.addr = addr;
    }

    @Override
    public String toString() {
        return "Address{" +
                "addr='" + addr + '\'' +
                '}';
    }

    @Override
    public Address clone() {
        Address address = null;
        try {
            address = (Address) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return address;
    }
}
// 输出结果:
User{name='xffjs', address=Address{addr='小飞'}}
User{name='xffjs', address=Address{addr='小飞'}}

可以发现上面修改了user的addr,但是cloneUser也跟着改变了。
原因是浅复制只是复制了addr变量的引用,并没有真正的开辟另一块空间,将值复制后再将引用返回给新对象。

我们修改user里面的clone方法如下:

@Override
public User clone() {
    User user = null;
    try {
        // 浅复制
        user = (User) super.clone();
    } catch (CloneNotSupportedException e) {
        e.printStackTrace();
    }
    // 深复制
    user.address = (Address) address.clone();
    return user;
}
// 输出结果:
User{name='xffjs', address=Address{addr='小飞'}}
User{name='xffjs', address=Address{addr='小飞博客'}}

总结

1、浅克隆

在浅克隆中,如果原型对象的成员变量值类型,将复制一份给克隆对象;如果原型对象的成员变量引用类型,则将引用对象的地址复制一份给克隆对象。

简单来说,在浅克隆中,当对象被复制时只复制它本身和其中包含的值类型的成员变量,而引用类型的成员对象并没有复制。

2、深克隆

在深克隆中,无论原型对象的成员变量是值类型还是引用类型,都将复制一份给克隆对象,深克隆将原型对象的所有引用对象也复制一份给克隆对象。

在Java语言中,如果需要实现深克隆,可以通过覆盖Object类的clone()方法实现,也可以通过序列化(Serialization)等方式来实现。

通过实现Serializable接口克隆

public class CloneTest {

    public static void main(String[] args) {
        // 创建User对象
        User user = new User();
        user.setName("xffjs");
        // 创建Address对象并set给user
        Address address = new Address();
        address.setAddr("小飞博客");
        user.setAddress(address);
        // 克隆对象
        User cloneUser = user.myclone();
        // 修改Address
        address.setAddr("小飞");
        System.out.println(user);
        System.out.println(cloneUser);
    }
}

class User implements Serializable {
    // 最好是显式声明ID
    private static final long serialVersionUID = 1L;

    String name;
    Address address;

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

    public void setAddress(Address address) {
        this.address = address;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", address=" + address +
                '}';
    }

    public User myclone() {
        User user = null;
        try {
            // 将该对象序列化成流,因为写在流里的是对象的一个拷贝,而原对象仍然存在于JVM里面。所以利用这个特性可以实现对象的深拷贝
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(baos);
            oos.writeObject(this);
            // 将流序列化成对象
            ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bais);
            user = (User) ois.readObject();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return user;
    }

}

class Address implements Serializable {
    // 最好是显式声明ID
    private static final long serialVersionUID = 1L;

    String addr;

    public void setAddr(String addr) {
        this.addr = addr;
    }

    @Override
    public String toString() {
        return "Address{" +
                "addr='" + addr + '\'' +
                '}';
    }
}