Java进阶 - 对象克隆

如果你想复制一个简单变量。很简单:

int x = 7;  
int y = x;

不仅仅是int类型,其它七种原始数据类型(boolean,char,byte,short,float,double.long)同样适用于该类情况。

但是如果你复制的是一个对象,情况就有些复杂了。

当我说我是一个beginner的时候,我会这样写:

class Car{

    private String color;
    private String name;

    Car(String color, String name) {
        this.color = color;
        this.name = name;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    public String getName() {
        return name;
    }

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

    @Override
    public String toString() {
        return "Car [color=" + color + ", name=" + name + "]";
    }
}

public class Test{
public static void main(String[] args) throws Exception {
Car car
= new Car("白色", "奔驰"); Car cloneCar = car; System.out.println("car = " + car); System.out.println("cloneCar = " + cloneCar); } }

结果:

car = Car [color=白色, name=奔驰]

cloneCar = Car [color=白色, name=奔驰]

这里我们自定义了一个Car类,该类有color和name字段。

我们新建了一个Car实例,然后将该值赋值给cloneCar实例。(Car cloneCar = car;)

再看看打印结果,作为一个新手,拍了拍胸腹,对象复制不过如此!

难道真的是这样吗?我们试着改变cloneCar实例的name字段,再打印结果看看:

cloneCar.setName("大众");
System.out.println("car = " + car);
System.out.println("cloneCar = " + cloneCar);

结果:

car = Car [color=白色, name=大众]

cloneCar = Car [color=白色, name=大众]

为什么改变cloneCar属性,car属性也发生了变化呢?

原因出在(cloneCar = car) 这一句。该语句的作用是将car的引用赋值给cloneCar,这样,car和cloneCar指向内存堆中同一个对象。

那么才能复制一个对象呢?

这就要用到我们的Object类了。它有11个方法,有两个protected的方法,其中一个为clone方法。

在Java中所有的类都是缺省的继承自Java语言包中的Object类的,查看它的源码。你会发现里面有一个访问限定符为protected的方法clone():

/* 
   Creates and returns a copy of this object. The precise meaning of "copy" may depend on the class of the object.
   The general intent is that, for any object x, the expression:
   1) x.clone() != x will be true
      保证克隆对象将有单独的内存地址分配。
   2) x.clone().getClass() == x.getClass() will be true, but these are not absolute requirements.
      原始和克隆的对象应该具有相同的类类型,但它不是强制性的。
   3) x.clone().equals(x) will be true, this is not an absolute requirement. 
     原始和克隆的对象应该是平等的equals()方法使用,但它不是强制性的。
*/
protected native Object clone() throws CloneNotSupportedException;

仔细一看,它还是一个native方法,大家都知道native方法是非Java语言实现的代码,供Java程序调用的,因为Java程序是运行在JVM虚拟机上面的,要想访问到比较底层的与操作系统相关的就没办法了,只能由靠近操作系统的语言来实现。

因为每个类直接或间接的父类都是Object,因此它们都含有clone()方法,但是因为该方法是protected,所以都不能在类外进行访问。

要想对一个对象进行复制,就需要对clone方法覆盖。

一、为什么要克隆?

为什么需要克隆对象?直接new一个对象不行吗?

答案是:克隆的对象可能包含一些已经修改过的属性,而new出来的对象的属性都还是初始化时候的值,所以当需要一个新的对象来保存当前对象的“状态”就靠clone方法了。

那么我把这个对象的临时属性一个一个的赋值给我新new的对象不也行嘛?可以是可以,但是一来麻烦不说,二来,大家通过上面的源码都发现了clone 是一个 native 方法,就是快啊,在底层实现的。

常见的 Object a = new Object();Object b = a;这种形式的代码复制的是引用,即对象在内存中的地址,a和b对象仍然指向了同一个对象。

而通过clone方法赋值的对象跟原来的对象时同时独立存在的。

二、如何实现克隆

Java中有两种不同的克隆方法:浅克隆(ShallowClone)和深克隆(DeepClone)

(1)浅克隆:实现Cloneable接口并重写Object类中的clone()方法;

(2)深克隆:实现Serializable接口,通过对象的序列化和反序列化实现克隆;

Java语言提供的Cloneable接口和Serializable接口的代码非常简单,它们都是空接口,这种空接口也称为标识接口,标识接口中没有任何方法的定义,其作用是告诉JRE这些接口的实现类是否具有某个功能,如是否支持克隆、是否支持序列化等。

- 注意:基于序列化和反序列化实现的克隆不仅仅是深度克隆,更重要的是通过泛型限定,可以检查出要克隆的对象是否支持序列化,这项检查是编译器完成的,不是在运行时抛出异常,这种是方案明显优于使用Object类的clone方法克隆对象。让问题在编译的时候暴露出来总是优于把问题留到运行时。

三、浅克隆

Car类:

class Car implements Cloneable {
    private String color;
    private String name;

    Car() {
        super();
    }

    Car(String color, String name) {
        this.color = color;
        this.name = name;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    public String getName() {
        return name;
    }

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

    @Override
    public String toString() {
        return "Car [color=" + color + ", name=" + name + "]";
    }
}
Car.java

Persion类:实现Cloneable接口,需要重写clone方法。

class Persion implements Cloneable {

    private int age;
    private String name;
    private Car car;

    public Persion() {
        super();
    }

    public Persion(int age, String name, Car car) {
        super();
        this.age = age;
        this.name = name;
        this.car = car;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

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

    public Car getCar() {
        return car;
    }

    public void setCar(Car car) {
        this.car = car;
    }

    @Override
    public String toString() {
        return "Persion [age=" + age + ", name=" + name + ", car=" + car + "]";
    }

    /**
     *     实现Cloneable接口,并重写clone方法
     */
    @Override
    protected Object clone() throws CloneNotSupportedException {
        Persion persion = null;
        try {
            /**
             *     若要实现深克隆,此处就必须将对象中所有的复合数据类型统统单独复制拷贝一份, 但是实际开发中,无法确定对象中复合数据的种类和个数,
             *     因此一般不采用此种方式实现深克隆
             */
            persion = (Persion) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return persion;
    }
}
Persion.java

测试类:

public class Test {

    public static void main(String[] args) throws Exception {

        Car car = new Car("白色", "奔驰");
        Persion p1 = new Persion(24, "阿大", car);

        Persion p2 = (Persion) p1.clone();
        p2.setAge(17);
        p2.setName("小黄");
        p2.getCar().setName("大众");

        System.out.println("p1 == p2 ? " + (p1 == p2));
        System.out.println(p1);
        System.out.println(p2);
    }
}

结果:小黄将阿大的奔驰变成大众。

修改基础数据结构数值不会影响其他对象,修改复合数据数值,全部都会受影响;

四、深克隆

Car类:必须实现Serializable接口

class Car implements Serializable {
    private static final long serialVersionUID = -6593855835779505248L;

    private String color;
    private String name;

    Car() {
        super();
    }

    Car(String color, String name) {
        this.color = color;
        this.name = name;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    public String getName() {
        return name;
    }

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

    @Override
    public String toString() {
        return "Car [color=" + color + ", name=" + name + "]";
    }
}
Car.java

Persion类:必须实现Serializable接口

class Persion implements Serializable {

    private static final long serialVersionUID = -630014110607564384L;

    private int age;
    private String name;
    private Car car;

    public Persion() {
        super();
    }

    public Persion(int age, String name, Car car) {
        super();
        this.age = age;
        this.name = name;
        this.car = car;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

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

    public Car getCar() {
        return car;
    }

    public void setCar(Car car) {
        this.car = car;
    }

    @Override
    public String toString() {
        return "Persion [age=" + age + ", name=" + name + ", car=" + car + "]";
    }

}
Persion.java

CloneUtil - 克隆工具类:

class CloneUtil {

    @SuppressWarnings("unchecked")
    public static <T extends Serializable> T clone(T object) throws Exception {
        ByteArrayOutputStream bout = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bout);
        oos.writeObject(object);

        ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bin);
        // 此处不需要释放资源,说明:调用ByteArrayInputStream或ByteArrayOutputStream对象的close方法没有任何意义
        // 这两个基于内存的流只要垃圾回收器清理对象就能够释放资源,这一点不同于对外部资源(如文件流)的释放
        return (T) ois.readObject();
    }
}

测试:

public class Test{

    public static void main(String[] args) throws Exception {

        Car car = new Car("白色", "奔驰");
        Persion p1 = new Persion(24, "阿大", car);

        Persion p2 = CloneUtil.clone(p1);
        p2.setAge(17);
        p2.setName("小黄");
        p2.getCar().setName("大众");

        System.out.println("p1 == p2 ? " + (p1 == p2));
        System.out.println(p1);
        System.out.println(p2);
    }
}

结果:奔驰还是奔驰,大众还是大众。

由输出结果来看,p2对象的car值修改不影响p1对象的car值,即使用序列化可以实现对象的深拷贝。

posted @ 2020-03-19 17:40  ohmok  阅读(237)  评论(0)    收藏  举报