当前位置: 首页 > 编程笔记 >

java 深拷贝与浅拷贝机制详解

卫诚
2023-03-14
本文向大家介绍java 深拷贝与浅拷贝机制详解,包括了java 深拷贝与浅拷贝机制详解的使用技巧和注意事项,需要的朋友参考一下

 java 深拷贝与浅拷贝机制详解

概要:

在Java中,拷贝分为深拷贝和浅拷贝两种。java在公共超类Object中实现了一种叫做clone的方法,这种方法clone出来的新对象为浅拷贝,而通过自己定义的clone方法为深拷贝。

(一)Object中clone方法

如果我们new出一个新对象,用一个声明去引用它,之后又用另一个声明去引用前一个声明,那么最后的结果是:这两个声明的变量将指向同一个对象,一处被改全部被改。如果我们想创建一个对象的copy,这个copy和对象的各种属性完全相同,而且修改这个copy和原对象毫无关系,那么这个时候我们就要用到clone方法。

package Clone;

import java.util.Date;

/**
 * 
 * @author QuinnNorris 
 * java中的两种拷贝机制
 */
public class Clone {

  /**
   * @param args
   * @throws CloneNotSupportedException 
   */
  public static void main(String[] args) throws CloneNotSupportedException {
    // TODO Auto-generated method stub

    ClassA valA = new ClassA(1, "old", new Date());
    // 声明一个新的ClassA对象,我们不需要太关注ClassA的功能
    ClassA valB = valA;
    // 将valA引用的对象赋给valB
    valA.setObject("new");
    // 更改valA中的值,此时valB也被更改了,因为valA和valB指向同一个对象

    valB = valA.clone();//通过clone方法制造副本
  }
}

ClassA类中关于clone方法的重写部分:

//需要实现Cloneable接口
public class ClassA implements Cloneable {

  public ClassA clone() throws CloneNotSupportedException {
    return (ClassA) super.clone();//调用父类(Object)的clone方法
  }

}

1.如何使用Object中clone方法的

有人总结使用clone方法的四条法则,我们一起分享一下:

  1. 为了获取对象的一份拷贝,我们可以利用Object类的clone()方法。
  2. 在派生类中覆盖基类的clone()方法,并声明为public。
  3. 在派生类的clone()方法中,调用super.clone()。
  4. 在派生类中实现Cloneable接口。

 2.protected修饰的clone方法

在java.lang.Object的中,他将clone方法设置为protected修饰,这是很特殊的一种情况。protected的作用域是:包可见+可继承。之所以这样设置,是因为这个方法要返回的是克隆出来的对象,即clone方法要去克隆的类型是未知的,没有办法确定返回值的类型,自然只能让子孙后代来实现它重写它,为了能够让后代继承而又不过与张开,设置为了protected类型。

3.实现clone方法需要实现Cloneable接口

那么我们重写clone方法的时候为什么要去实现Cloneable接口呢?事实上,Cloneable接口是java中的一个标记接口,标记接口是指那些没有方法和属性的接口,他们存在只是为了让大家知道一些信息,而且在用:xxx instanceof Cloneable 的时候可以进行判断。Cloneable这个接口的出现就是为了让设计者知道要进行克隆处理了。如果一个对象需要克隆,但是没有实现(实际上,这里的“实现”换成“写上”更准确)Cloneable接口,那么会产生一个已检验异常。

4.实现clone方法需要调用父类的clone

我们为了达到复制一个和调用方法的这个对象一模一样的对象的目的,我们需要使用父类的clone方法,父类也以此类推,知道达到了Object的clone方法,那么Object的clone方法有什么用呢?API中是这样说的:

protected Object clone( ) throws CloneNotSupportedException
创建并返回此对象的一个副本。
“副本”的准确含义可能依赖于对象的类。这样做的目的是,对于任何对象 x,
表达式: x.clone() != x为 true,
表达式: x.clone().getClass() == x.getClass()也为 true,
但这些并非必须要满足的要求。
一般情况下:
x.clone().equals(x)为 true,但这并非必须要满足的要求。
按照惯例,返回的对象应该通过调用 super.clone 获得。
如果一个类及其所有的超类(Object 除外)都遵守此约定,则 x.clone().getClass() == x.getClass()。

上面就是API中对clone的一部分基本讲解。我们可以得出结论的是,只要合理的调用了spuer.clone( )它就会返回一个被克隆的对象。在运行时刻,Object中的clone()识别出你要复制的是哪一个对象,然后为此对象分配空间,并进行对象的复制,将原始对象的内容一一复制到新对象的存储空间中。在这个克隆对象中,所有的属性都和被克隆的对象的属性相同,而这些相同的属性分为两种:

第一种 : 八大原始类型和不可变的对象(比如String)
第二种 : 其他类对象

对于第一种,clone方法将他们的值设置为原对象的值,没有任何问题。对于第二种,clone方法只是简单的将复制的新对象的引用指向原对象指向的引用,第二种的类对象会被两个对象修改。那么这个时候就涉及一个深浅拷贝的概念了。

(二)浅拷贝

浅拷贝:被拷贝对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。换言之,浅复制仅仅复制所考虑的对象,而不复制它所引用的对象。

比如举个例子,一个类A中有另外一个类B类型的变量。在A重写clonehtml" target="_blank">函数调用super.clone的时候,创建的新对象和原来对象中的类B类型的变量是同一个,他们指向了同一个B的类型变量。如果在A中对B的变量做了修改,在新的拷贝出来的对象中B的变量也会被同样的修改。
请记住,直接调用super.clone实现的clone方法全部都是浅拷贝。

(三)深拷贝

深拷贝:被拷贝对象的所有变量都含有与原来的对象相同的值,除去那些引用其他对象的变量。那些引用其他对象的变量将指向被复制过的新对象,而不再是原有的那些被引用的对象。换言之,深复制把要复制的对象所引用的对象都复制了一遍。

通俗的说,如果说浅拷贝,开始的时候是两条线,如果在最后有一个其他类的变量,那么这两条线最后会合二为一,共同指向这变量,都能对他进行操作。深拷贝则是完完全全的两条线,互不干涉,因为他已经把所有的内部中的变量的对象全都复制一遍了。

深拷贝在代码中,需要在clone方法中多书写调用这个类中其他类的变量的clone函数。

(四)串行化深拷贝

在框架中,有的时候我们发现其中并没有重写clone方法,那么我们在需要拷贝一个对象的时候是如何去操作的呢?答案是我们经常会使用串行化方法,实现Serializable接口。

去寻找其他的方法来替代深拷贝也是无可奈何的事情,如果采用传统的深拷贝,难道你拷贝一个对象的时候向其中追无数层来拷贝完所有的对象变量么?先不谈这么做的时间消耗,仅仅是写这样的代码都会让人望而生畏。串行化深拷贝就是这样一个相对简单的方法。

把对象写到流里的过程是串行化(Serilization)过程,但是在Java程序师圈子里又非常形象地称为“冷冻”或者“腌咸菜(picking)”过程;而把对象从流中读出来的并行化(Deserialization)过程则叫做 “解冻”或者“回鲜(depicking)”过程。应当指出的是,写在流里的是对象的一个拷贝,而原对象仍然存在于JVM里面,因此“腌成咸菜”的只是对象的一个拷贝,Java咸菜还可以回鲜。

上面是网上的专业解释,我也不在这里班门弄斧了。在Java语言里深复制一个对象,常常可以先使对象实现Serializable接口,然后把对象(实际上只是对象的一个拷贝)写到一个流里(腌成咸菜),再从流里读出来(把咸菜回鲜),便可以重建对象。

public Object deepClone() 
{ 
 //写入对象 
 ByteArrayOutoutStream bo=new ByteArrayOutputStream(); 
 ObjectOutputStream oo=new ObjectOutputStream(bo); 
 oo.writeObject(this); 
 //读取对象
 ByteArrayInputStream bi=new ByteArrayInputStream(bo.toByteArray()); 
 ObjectInputStream oi=new ObjectInputStream(bi); 
 return(oi.readObject()); 
}

虽然这种学院派的代码看起来很复杂,其实只是把对象放到流里,再拿出来。相比较分析判断无数的clone,这样简直是再简单不过了。这样做的前提是对象以及对象内部所有引用到的对象都是可串行化的,否则,就需要仔细考察那些不可串行化的对象是否设成transient。

transient:一个对象只要实现了Serilizable接口,这个对象就可以被序列化(序列化是指将java代码以字节序列的形式写出,即我们上面代码前三行写入对象),Java的这种序列化模式为开发者提供了很多便利,可以不必关系具体序列化的过程,只要这个类实现了Serilizable接口,这个的所有属性和方法都会自动序列化。但是有种情况是有些属性是不需要序列号的,所以就用到这个关键字。只需要实现Serilizable接口,将不需要序列化的属性前添加关键字transient,序列化对象的时候,这个属性就不会序列化到指定的目的地中。

(五)总结

在实际的应用中,深拷贝和浅拷贝只是两个概念,不一定谁比谁好,要按照实际的工作来确定如何去拷贝一个对象。如果在数据库操作方面,为了取出一张表时不涉及其他的表,肯定需要使用浅拷贝,而在框架的Serializable中,虽然耗时,但是深拷贝是非常有必要的。

 感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!

 类似资料:
  • 本文向大家介绍javascript深拷贝和浅拷贝详解,包括了javascript深拷贝和浅拷贝详解的使用技巧和注意事项,需要的朋友参考一下 一、数组的深浅拷贝 在使用JavaScript对数组进行操作的时候,我们经常需要将数组进行备份,事实证明如果只是简单的将它赋予其他变量,那么我们只要更改其中的任何一个,然后其他的也会跟着改变,这就导致了问题的发生。 这是为什么呢? 因为如果只是简单的赋值,它只

  • 一、引言 对象拷贝(Object Copy)就是将一个对象的属性拷贝到另一个有着相同类类型的对象中去。在程序中拷贝对象是很常见的,主要是为了在新的上下文环境中复用对象的部分或全部数据。Java中有三种类型的对象拷贝:浅拷贝(Shallow Copy)、深拷贝(Deep Copy)、延迟拷贝(Lazy Copy)。 二、浅拷贝 1、什么是浅拷贝 浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着

  • 本文向大家介绍深拷贝与 浅拷贝的区别?相关面试题,主要包含被问及深拷贝与 浅拷贝的区别?时的应答技巧和注意事项,需要的朋友参考一下 浅克隆:创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原有属性所指向的对象的内存地址。 深克隆:创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址。

  • 本文向大家介绍浅谈Python浅拷贝、深拷贝及引用机制,包括了浅谈Python浅拷贝、深拷贝及引用机制的使用技巧和注意事项,需要的朋友参考一下 这礼拜碰到一些问题,然后意识到基础知识一段时间没巩固的话,还是有遗忘的部分,还是需要温习,这里做份笔记,记录一下 前续 先简单描述下碰到的题目,要求是写出2个print的结果 可以看到,a指向了一个列表list对象,在Python中,这样的赋值语句,其实内

  • 主要内容:到底是浅拷贝还是深拷贝对于基本类型的数据以及简单的对象,它们之间的拷贝非常简单,就是按位复制内存。例如: b 和 obj2 都是以拷贝的方式初始化的,具体来说,就是将 a 和 obj1 所在内存中的数据按照二进制位(Bit)复制到 b 和 obj2 所在的内存, 这种默认的拷贝行为就是 浅拷贝 ,这和调用 memcpy() 函数的效果非常类似。 对于简单的类,默认的拷贝构造函数一般就够用了,我们也没有必要再显式地定义一

  • 浅拷贝 对于对象或数组类型,当我们将a赋值给b,然后更改b中的属性,a也会随着变化。 也就是说,a和b指向了同一块堆内存,所以修改其中任意的值,另一个值都会随之变化,这就是浅拷贝。 深拷贝 那么相应的,如果给b放到新的内存中,将a的各个属性都复制到新内存里,就是深拷贝。 也就是说,当b中的属性有变化的时候,a内的属性不会发生变化。 参考链接: 深拷贝与浅拷贝的实现(一) javaScript中浅拷