美文网首页
浅谈Java的深拷贝与浅拷贝的雷区

浅谈Java的深拷贝与浅拷贝的雷区

作者: 路万奇与青川君 | 来源:发表于2018-05-28 23:38 被阅读0次

深拷贝与浅拷贝初探

先说说浅拷贝...

一个类的拷贝构造方法通常实现为成员变量逐域赋值,即将当前对象的各个成员变量赋值为实际参数对应的各个成员变量的 值 ,称为浅拷贝。

浅拷贝有什么弱点呢?
① 当成员变量的数据类型是基本数据类型时,(int float double char boolean)
浅拷贝能够实现对象的复制功能,这个复制是完整的,没有隐患的。
② 当成员变量是引用数据类型时,浅拷贝仅仅只是复制了 这一层引用关系。
而并没有实现对象的复制功能,这个复制是有隐患的。

TheClass(SomeClass ptr_obj) //拷贝构造方法
{
this.MemberVar = ptr_obj.MemberVar;
//逐个域赋值成员变量 初始化当前实例
}

这个隐患在于:当两个引用同时指向一个对象时,例如当 SeqList1 的成员数组 element1,
经过浅拷贝后的对象 SeqList2 的成员数组变量 element2,这两个引用变量引用的是同一个数组。
当在进行 element1 的数组元素的修改时(修改包括:插入、删除...),
实际上也影响了 element2 的元素,但是并没有任何代码去更改 element2 的长度。

public SeqList(SeqList<T>list){
thsi.n = list.n;
this.element = list.element;    //两者引用同一个数组
}

下面我们举个例子:

String[] values = {"a","b","c","d"};
SeqList<String> list1 = new SeqList<String>(values);  //正常地通过传入数组初始化
SeqList<String> list2 = new SeqList<String>(list1);   //通过拷贝方法来初始化

list1.remove(0);
System.out.println("list1 :" + list1.toString());
System.out.println("list2 :" + list2.toString());

因为删除了数组的第一项,但 list2 自己并不知晓这一点,依然在 toString() 时会输出 element[0] 这一项,所以运行时在输出 list2 时会报错:

list1 : {b,c,d}
Exception in thread "main" java.lang.NullPointerException

所以为了消除这种隐患,我们需要深拷贝。

再来说说深拷贝...


看了上面浅拷贝的同学们一定心里有数,浅拷贝的弱点就是没有实际拷贝 引用指向的那个对象或是变量,究其原因就是因为没有 新开辟内存空间来存放一个复制得到的新对象。

所以深拷贝不仅要做到浅拷贝里做到的拷贝对象的各个成员变量的值,还要为引用变量新开辟内存空间,并复制其中的所有元素或是对象。

但是 深拷贝中有一个很常见的雷区!
如下我贴出了一个并不正确的深拷贝构造方法:

public SeqList(SeqList<? extends T>list){
this.n = list.n;   //基础类型变量用 赋值 来 复制
this.element = new Object[list.element.length]; //开辟空间
// 但这个空间并没有任何实际意义,因为池子里什么也没有

for(int i=0; i<list.n; i++){
this.element[i] = list.element[i];   
// 其实拷贝的还是引用关系,别忘了 C/C++ 和 java 中
// xxx[yyy] 表示的仍是一个指针引用关系,其实还是一个地址指针/引用
// 引用的是 list.element 对应的数组元素。
}
}
SeqList<StringBuffer>lista = new SeqList<StringBuffer>(n-1);
for(int i=0; i<n; ++i){
lista.insert(new StringBuffer((char)('A'+i)+""));
}

// 通过 假的"深拷贝" 构造:
SeqList<StringBuffer>listb = new SeqList<StringBuffer>(lista);


lista.insert(new StringBuffer("F"));
listb.remove(listb.size()-1);
// 插入和删除 改变的只是添一个指针和砍掉一个指针而已
// 所以 lista 的改变对 listb 没有影响

StringBuffer strbuf = lista.get(0);
strbuf.setCharAt(0,'X');
lista.set(0,strbuf);
// 涉及到了对象引用的元素的实例值。

此时,lista 和 listb 引用的是同一个池子中(同一块内存地址)。
这一组元素实例值 = {a,b,c,d}
假如此时,将 lista[0] 修改为 "x",仍然会影响到 listb.

最终...


这里有一份比较直观的图例:

深拷贝

从此 list1 和 list2 就是完全独立,不相干的两个变量了。
当对数组元素做修改时,他们修改的都是自己储存空间里放的那一列元素。

可具体要怎么做才能实现深拷贝呢?
我们有所有类的爸爸 Object 呀!
它有11个方法,有两个protected的方法,其中一个为clone方法。

该方法声明如下:

protected native Object clone() throws CloneNotSupportedException;

因为每个类直接或间接的父类都是Object,因此它们都含有clone()方法,但是因为该方法是protected,所以都不能在类外进行访问。
要想对一个对象进行复制,就需要对clone方法覆盖。

@Override 
public Object clone() { 
SeqList toList = null; 
try{ 
toList = (SeqList)super.clone(); 
// 这里用到了 对父类的向下转型
}catch(CloneNotSupportedException e) { 
e.printStackTrace(); 
} 
return toList; 
} 

总结

所以深拷贝应该是,复制每个引用类型成员变量所引用的数组或是对象,
直到该对象能达到的所有对象及实例。

深拷贝/浅拷贝看上去是一个非常小非常细的知识点,但是其实能够体现很多
类、对象和方法的基础思想。
这是我第一次系统性的总结一个知识点,希望以后能做得更好。

具体针对clone方法,我们还有一大堆可以说的。
敬请期待下回分解。

相关文章

  • 深拷贝和浅拷贝

    1: iOS开发 深拷贝与浅拷贝 2: iOS 浅谈:深.浅拷贝与copy.strong 3: iOS开发——深...

  • 浅谈Java的深拷贝与浅拷贝的雷区

    深拷贝与浅拷贝初探 先说说浅拷贝... 一个类的拷贝构造方法通常实现为成员变量逐域赋值,即将当前对象的各个成员变量...

  • copy和mutablecopy

    Objective-C中的浅拷贝和深拷贝 - CocoaChina_让移动开发更简单 iOS 浅谈:深.浅拷贝与c...

  • java 对象的拷贝

    拷贝:即复制 对象拷贝:即对象复制 java 对象拷贝分类:浅拷贝、深拷贝 java 对象的浅拷贝和深拷贝针对包含...

  • Java基础 - 深拷贝和浅拷贝

    Java 的深拷贝和浅拷贝 什么是深拷贝、浅拷贝 (深克隆、浅克隆)? 在 Java 中,数据类型分为 基本数据类...

  • 浅谈java浅拷贝与深拷贝

    java实现拷贝最直观的做法用object类中的clone()方法,而想要使用该方法进行对象的克隆只要实现clon...

  • Java------List的深拷贝与浅拷贝

    Java的浅拷贝(Shallow Copy)、深拷贝(Deep Copy)。 浅拷贝(Shallow Copy) ...

  • java中的深拷贝和浅拷贝

    简单记录一下java中的深拷贝和浅拷贝,深拷贝和浅拷贝只是针对对象而言的. 1 深拷贝代码 2 浅拷贝代码 3 测...

  • 浅谈 Java 浅拷贝&深拷贝

    深拷贝和浅拷贝的概念,我自己在学习Java的时候也没注意,虽然Java中对象回收工作由GC帮我们做了,但在码代码时...

  • JS中的深拷贝与浅拷贝

    知乎:js中的深拷贝和浅拷贝? 掘金: js 深拷贝 vs 浅拷贝 前言 首先深拷贝与浅拷贝只针对 Object,...

网友评论

      本文标题:浅谈Java的深拷贝与浅拷贝的雷区

      本文链接:https://www.haomeiwen.com/subject/rfonjftx.html