我们先增加一个饮品类 Drink 。
public class Drink implements Cloneable { String name; get and set() @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } toString() }然后更改一下 Food 类,因为 Drink 也算是 Food ,所以我们在 Food 类中增加对 Drink 的引用,然后再修改 get set 、toString 、clone 、构造方法,修改后的 Food 类代码如下
public class Food implements Cloneable{ String name; int num; String taste; Drink drink; public Food(String name, int num, String taste,Drink drink) { this.name = name; this.num = num; this.taste = taste; this.drink = drink; } get and set... @Override protected Object clone() throws CloneNotSupportedException { Food food = (Food)super.clone(); food.drink = (Drink) drink.clone(); return super.clone(); } @Override public String toString() { return "Food{" + "name='" + name + '\'' + ", num=" + num + ", taste='" + taste + '\'' + ", drink=" + drink + '}'; } }可以看到最大的改变是 clone 方法,我们在 clone 方法中,实现了对 Food 对象的拷贝,同时也实现了对 Drink 对象的拷贝,这就是我们上面所说的复制对象并复制对象的成员变量。
然后我们进行一下 Deep Copy的测试:
public static void main(String[] args) throws CloneNotSupportedException { Drink drink = new Drink("milk"); Food food = new Food("humberge",1,"fragrance",drink); Food foodClone = (Food)food.clone(); Drink tea = new Drink("tea"); food.setDrink(tea); System.out.println("food = " + food); System.out.println("foodClone = " + foodClone.getDrink()); }运行完成后的输出结果如下:
food = Food{name='humberge', num=1, taste='fragrance', drink=Drink{name='tea'}} foodClone = Drink{name='milk'}可以看到,我们把 foodClone 拷贝出来之后,修改 food 中的 drink 变量,却不会对 foodClone 造成改变,这就说明 foodClone 已经成功实现了深拷贝。
用图示表示的话,应该是下面这样的:
这是深拷贝之后的内存分配图,现在可以看到,food 和 foodClone 完全是两个不同的对象,它们之间不存在纽带关系。
我们上面主要探讨实现对象拷贝的方式是对象实现 Cloneable 接口,并且调用重写之后的 clone 方法,在 Java 中,还有一种实现对象拷贝的方式是使用 序列化。
序列化使用序列化的方式主要是使用 Serializable 接口,这种方式还以解决多层拷贝的问题,多层拷贝就是引用类型里面又有引用类型,层层嵌套下去。使用 Serializable 的关键代码如下
public Person clone() { Person person = null; try { ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); oos.writeObject(this); // 将流序列化成对象 ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bais); person = (Person) ois.readObject(); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } return person; }使用序列化可以实现深拷贝,它的原理是将二进制字节流内容写到一个文本或字节数组,然后是从这个文本或者字节数组中读取数据,原对象写入这个文本或者字节数组后再拷贝给 clone 对象,原对象的修改不会影响 clone 对象,因为 clone 对象是从文本或者字节数组中读取的。
如何选择拷贝方式到现在我们已经把浅拷贝和深拷贝都介绍完了,那么如何选择浅拷贝和深拷贝呢?下面是几点注意事项⚠️
如果对象的属性都是基本数据类型,那么可以使用浅拷贝。
如果对象有引用类型,那就要基于具体的需求来选择浅拷贝还是深拷贝。
如果对象嵌套层数比较多,推荐使用 Serializable 接口实现深拷贝。
如果对象引用任何时候都不会被改变,那么没必要使用深拷贝,只需要使用浅拷贝就行了。如果对象引用经常改变,那么就要使用深拷贝。没有一成不变的规则,一切都取决于具体需求。
其他拷贝方式除了对象的拷贝,Java 中还提供了其他的拷贝方式
比如数组的拷贝,你可以使用 Arrays.copyof 实现数组拷贝,还可以使用默认的 clone 进行拷贝,不过这两者都是浅拷贝。
public void test() { int[] lNumbers1 = new int[5]; int[] rNumbers1 = Arrays.copyOf(lNumbers1, lNumbers1.length); int[] lNumbers2 = new int[5]; int[] rNumbers2 = lNumbers2.clone(); }除了基本数组数据类型之外的拷贝,还有对象的拷贝,不过用法基本是一样的。
集合也可以实现拷贝,因为集合的底层就使用的是数组,所以用法也是一样的。
一些说明针对 Cloneable 接口,有下面三点使用说明
如果类实现了 Cloneable 接口,再调用 Object 的 clone() 方法可以合法地对该类实例进行按字段复制。
如果在没有实现 Cloneable 接口的实例上调用 Object 的 clone() 方法,则会导致抛出CloneNotSupporteddException。
实现此接口的类应该使用公共方法重写 Object 的clone() 方法,因为 Object 的 clone() 方法是一个受保护的方法。