java对象---深/浅拷贝
继原型模式的续,本文分享的是浅拷贝和深拷贝
深入了解浅拷贝与深拷贝
在学习深拷贝和浅拷贝之前,咱们先来一个例子;
- import java.util.ArrayList;
-
- public class MyBaby implements Cloneable {
- /**
- * 私有变量
- */
- private ArrayList<String> list = new ArrayList<>();
-
- @Override
- protected Object clone() throws CloneNotSupportedException {
- MyBaby myBaby = null;
- try {
- myBaby = (MyBaby) super.clone();
- } catch (CloneNotSupportedException ex) {
- ex.printStackTrace();
- }
- return myBaby;
- }
-
- /**
- * 给List设置
- *
- * @param value 值
- */
- public void setValue(String value) {
- this.list.add(value);
- }
-
- /**
- * 获取list
- *
- * @return list
- */
- public ArrayList<String> getValue() {
- return this.list;
- }
- }
在MyBaby类中有一个私有变量list,类似为List,然后咱们使用setValue对其进行设值,使用getValue进行取值。接下来咱们来看看他是如何拷贝的。
- public class TestMyBaby {
- public static void main(String[] args) {
- MyBaby baby = new MyBaby();
- baby.setValue("Java后端技术栈");
- try {
- MyBaby myBabyClone = (MyBaby) baby.clone();
- myBabyClone.setValue("咖啡");
-
- System.out.println(baby.getValue());
- } catch (CloneNotSupportedException e) {
- e.printStackTrace();
- }
- }
- }
猜想运行结果会是神马?
[Java后端技术栈, 咖啡]
怎么会这样呢?怎么会有“咖啡”呢?
是因为Java给我们做了一个偷懒性的拷贝动作,Object类原本就提供一个方法clone用来拷贝对象,因为其对象内部的数组、引用对象等都不拷贝,还是指向了原生对象的内部元素地址,这种拷贝就叫做浅拷贝。
浅拷贝
上面这个拷贝也太浅了吧,两个对象引用都boby、myBaby共享一个私有变量list,都可以对list进行改变,是一种非常不安全的方式。
再看一个例子;
- public class Person implements Cloneable {
-
- private int age;
- private String name;
-
- 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;
- }
-
- @Override
- protected Object clone() throws CloneNotSupportedException {
- Person person=null;
- try{
- person=(Person)super.clone();
- }catch (CloneNotSupportedException ex){
- ex.printStackTrace();
- }
- return person;
- }
- }
来写一个测试类,对其clone方法进行测试:
- public class TestPerson {
- public static void main(String[] args) throws CloneNotSupportedException {
- Person person = new Person();
- person.setAge(22);
- person.setName("Java后端技术栈");
- //克隆一个对象
- Person clone = (Person) person.clone();
- //对person的age重新赋值为25
- person.setAge(25);
- //对person的age重新赋值为25
- person.setName("咖啡");
- System.out.print(clone.getAge()+","+clone.getName());
- }
- }
运行后将输出什么呢?先猜想一下。
具体运行结果如下:
22,Java后端技术栈
是不是觉得很神奇呢?为什么没有变化呢?
原始数据类型会被拷贝,如果从原始数据类型考虑,因为age是int类型,int是原始数据类型,所以上述场景没变,那也就无话可说,但是String并不是原始数据类型,那又是为什么呢?因为String是一个特殊类型,因为这种场景下Java希望String也看成原始类型。所以String并没有clone方法。
- String aa="aa";
- aa.clone();
String定义
- public final class String
- implements java.io.Serializable, Comparable<String>, CharSequence {
- //...省略其他
- }
这段代码编译通不过,提示无法访问,为什么呢?请看Object源码中对clone方法的定义
protected native Object clone() throws CloneNotSupportedException;
String处理机制比较特殊,通过字符串池在需要的时候再内存中创建新的字符串,以后大家就在使用(clone)的时候就直接把String当做原始数据类型就行了。
深拷贝
浅拷贝是有风险的,那么如何才能深拷贝呢?我们对前面的Mybaby程序进行修改一下就成了深拷贝了;
- import java.util.ArrayList;
-
- /**
- * @author tianweichang
- * @date 2019/7/13
- */
- public class MyBaby implements Cloneable {
- /**
- * 私有变量
- */
- private ArrayList<String> list = new ArrayList<>();
-
- @SuppressWarnings("unchecked")
- @Override
- protected Object clone() throws CloneNotSupportedException {
- MyBaby myBaby = null;
- try {
- myBaby = (MyBaby) super.clone();
- //增加了一个list.clone();
- this.list = (ArrayList<String>) this.list.clone();
- } catch (CloneNotSupportedException ex) {
- ex.printStackTrace();
- }
- return myBaby;
- }
-
- /**
- * 给List设置
- *
- * @param value 值
- */
- public void setValue(String value) {
- this.list.add(value);
- }
-
- /**
- * 获取list
- *
- * @return list
- */
- public ArrayList<String> getValue() {
- return this.list;
- }
- }
再次运行TestMyBaby,结果:
[Java后端技术栈]
改短代码就实现了完全的拷贝,两个对象引用指向的就不再是同一个地址了。相互之间没有什么关系了,你修改你的,我修改我的,完全不会有什么安全问题。这就是深拷贝。
深拷贝还有一种实现方式:通过写自己的二进制流来操作对象,然后实现对象的深拷贝。
建议:
深拷贝和浅拷贝不要混合使用,特别是在涉及到类的继承时候,父类中有多个引用的情况下就会非常复杂,建议方案是深拷贝和浅拷贝分开实现。
clone与final两个冤家
对象的clone与对象内的final关键字是有冲突,前者是要重新赋值,后者是赋值了就不能变了。
咱们继续帮上忙的代码进行改造:
- public class MyBaby implements Cloneable {
- /**
- * 私有变量
- */
- private final ArrayList<String> list = new ArrayList<>();
-
- @SuppressWarnings("unchecked")
- @Override
- protected Object clone() throws CloneNotSupportedException {
- MyBaby myBaby = null;
- try {
- myBaby = (MyBaby) super.clone();
- //下面的this.list编译通不过,提示不能给final修饰的变量重新赋值
- this.list = (ArrayList<String>) this.list.clone();
- } catch (CloneNotSupportedException ex) {
- ex.printStackTrace();
- }
- return myBaby;
- }
-
- /**
- * 给List设置
- *
- * @param value 值
- */
- public void setValue(String value) {
- this.list.add(value);
- }
-
- /**
- * 获取list
- *
- * @return list
- */
- public ArrayList<String> getValue() {
- return this.list;
- }
- }
所以请注意,要使用clone方法的时候,类的成员变量上不要加final修饰
欢迎关注公众号:Java后端技术全栈