数组克隆及对象的深、浅克隆(deep clone、shallow clone)

2024-03-29 13:58

本文主要是介绍数组克隆及对象的深、浅克隆(deep clone、shallow clone),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

数组克隆及对象克隆的作用

在有的时候,我们需要分发出多个结构及内容相同,但各自间相互独立的实体,以用作业务需要(对于对象来说可能存在内部部分引用不独立的情况,此问题放在后面讨论)。比如说将数组 int[] body 或对象 Object body 拷贝出多份,分别命名为 body1,body2,body3,并且要求修改任何一个对象的时候其它对象均不会受到任何影响,此时就要用到克隆。


深克隆与浅克隆概念

先要说明的是,在java中数组被当作是对象来看待,同样也继承自Object类。


浅克隆(shallow clone)

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


深克隆(deep clone)

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


clone()方法

克隆数组有多种方法,而克隆对象则通过clone()方法来进行。同时,clone() 方法应当满足以下条件:

①对任何的对象body,都有body.clone() !=body//克隆对象与原对象不是同一个对象
②对任何的对象body,都有body.clone().getClass()= =body.getClass()//克隆对象与原对象的类型一样

③如果对象body的equals()方法定义恰当,那么body.clone().equals(body)应该成立。


克隆数组

克隆数组至少有以下四种思路:

1、使用Object类的clone()方法, 这种方法最简单,得到原数组的一个副本。灵活形也最差。效率最差,尤其是在数组元素很大或者复制对象数组时;

2、使用Systems的arraycopy()这种方法速度最快,并且灵活性也较好,可以指定原数组名称、以及元素的开始位置、复制的元素的个数,目标数组名称、目标数组的位置;

3、Arrays类的copyOf()方法与copyOfRange()方法可实现对数组的复制;

4、使用循环结构。这种方法最灵活。唯一不足的地方可能就是代码较多。

在此还得先普及几个知识:

1、java中没有“二维数组”、“n纬数组”这种说法,因此java数组的概念并不同等于C中的数组。

在java中所谓的二维数组,应当叫做“数组的数组”。例如:

int arr[][] = {{111,222,333}, {444,555,666}, {777,888,999}};

那么,arr[0][0]~arr[0][2]则成为“该数组中的第0组数组”。

2、java数组并不一定是矩形的或者是正体结构的,java允许不规则的数组结构体存在。例如,我可以这样声明数组:

int arr[][] = {{111,222,333}, {666}, {777,888,999,11,22}};
此时该数组的内存分配将会是这样的:


如果我试图访问arr[1][1],必然会出现java.lang.ArrayIndexOutOfBoundsException异常。


使用Object.clone()进行克隆

我们通过一小段代码来展示:

	public static void main(String[] args) {int arr[] = {0,1,2,3,4,5};int copyarr[] = null;copyarr = arr.clone();copyarr[2] = 2048;System.out.println("arr == copyarr? " + (arr == copyarr));System.out.println("arr[2] is " + arr[2]);System.out.println("copyarr[2] is " + copyarr[2]);}
该段代码输出结果为:

arr == copyarr? false
arr[2] is 2
copyarr[2] is 2048

显然,这就是我们所预期的结果。arr通过clone()方法克隆出了新的数组,使得arr与copyarr并不引用同一个对象,因此对copyarr所做出的任何更改都将不会影响到arr本身。

与普通循环赋值创建新数组对比,该方法效率相对较高,因为Object.clone()调用的是本地方法:

protected native Object clone() throws CloneNotSupportedException;

使用System.arraycopy()进行克隆

我们在刚才的例子上做少许改动:

	public static void main(String[] args) {int arr[] = {0,1,2,3,4,5};int copyarr[] = new int[6];System.arraycopy(arr, 0, copyarr, 0, arr.length);copyarr[2] = 2048;System.out.println("arr == copyarr? " + (arr == copyarr));System.out.println("arr[2] is " + arr[2]);System.out.println("copyarr[2] is " + copyarr[2]);}
与Object.clone()不同,System.arraycopy()并不是返回一个新的数组,而是要求提供一个已经初始化的数组,并根据参数对新数组进行改动。查看调用方法,该方法亦是调用本地方法完成的:

    public static native void arraycopy(Object src,  int  srcPos,Object dest, int destPos,int length);

使用Arrays.copyOf() / Arrays.copyOfRange()进行克隆

这两个方法无论使用的是哪个重载,最终都将是调用System.arraycopy()方法。Arrays类中的copy方法丰富及简化了拷贝的使用。

Arrays.copyOf()的其中一个重载方法:

    public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {T[] copy = ((Object)newType == (Object)Object[].class)? (T[]) new Object[newLength]: (T[]) Array.newInstance(newType.getComponentType(), newLength);System.arraycopy(original, 0, copy, 0,Math.min(original.length, newLength));return copy;}
Arrays.copyOfRange()的其中一个重载方法:

    public static <T,U> T[] copyOfRange(U[] original, int from, int to, Class<? extends T[]> newType) {int newLength = to - from;if (newLength < 0)throw new IllegalArgumentException(from + " > " + to);T[] copy = ((Object)newType == (Object)Object[].class)? (T[]) new Object[newLength]: (T[]) Array.newInstance(newType.getComponentType(), newLength);System.arraycopy(original, from, copy, 0,Math.min(original.length - from, newLength));return copy;}

使用循环结构进行克隆

接下来是数组拷贝中最具探讨性的内容。

现在,我们来尝试克隆二维数组,使用的方法是上面所提到的克隆方法。代码如下:

public static void main(String[] args) {int arr[][] = {{111,222,333},{666}};int copyarr[][] = new int[2][3];copyarr  = arr.clone();copyarr[0][0] = 9090;System.out.println("arr == copyarr? " + (arr == copyarr));System.out.println("arr[0] == b[0]? " + (arr[0] == copyarr[0]));System.out.println("arr[0][0] is " + arr[0][0]);System.out.println("b[0][0] is " + copyarr[0][0]);}
输出如下:

arr == copyarr? false
arr[0] == b[0]? true
arr[0][0] is 9090
b[0][0] is 9090

奇怪了,这似乎不是我们想要的结果!

我们发现,数组arr和copyarr并不是同一个对象,但是他们的子数组却指向同一个对象,因此导致了改变其中一个数组的值,导致另一个数组随之改变。

别忘了,在java中并没有二维数组的概念。每一个数组都是一个对象,而数组的数组,则是这个数组里面的一个对象而已。因此,如果你在类似上述例子的数组使用克隆,从而导致了只克隆了第一层而没有克隆内部数组,这种现象我们称之为浅克隆。请查看浅克隆的定义,在这里我们正好符合。

如果想要做到整个数组完全被克隆,则需要对数组中的数组进行克隆。同样,如果存在数组的数组的数组,那么则要继续深入内部进行克隆。这种情况下我们可以使用循环进行克隆,代码如下:

	public static void main(String[] args) {int arr[][] = {{111,222,333},{666}};int copyarr[][] = new int[2][3];for (int i = 0; i < arr.length; i++) {copyarr[i] = arr[i].clone();}copyarr[0][0] = 9090;System.out.println("arr == copyarr? " + (arr == copyarr));System.out.println("arr[0] == copyarr[0]? " + (arr[0] == copyarr[0]));System.out.println("arr[0][0] is " + arr[0][0]);System.out.println("copyarr[0][0] is " + copyarr[0][0]);}
输出如下:

arr == copyarr? false
arr[0] == copyarr[0]? false
arr[0][0] is 111
copyarr[0][0] is 9090

数组arr与数组copyarr的全部引用均不指向同一个地址,这样我们便完成了深克隆。当然,如果你的数组结构极为复杂,则可以使用迭代方式进行循环,在此不进行演示。

以上部分内容转载或参考来源如下:

http://www.cppblog.com/baby-fly/archive/2010/11/16/133763.html?opt=admin

http://greemranqq.iteye.com/blog/1750028

在此表示感谢。
转载请注明来源,版权归原作者所有,未经同意严禁用于任何商业用途。
微博:http://weibo.com/theworldsong
邮箱:theworldsong@foxmail.com


这篇关于数组克隆及对象的深、浅克隆(deep clone、shallow clone)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/858773

相关文章

Java中的数组与集合基本用法详解

《Java中的数组与集合基本用法详解》本文介绍了Java数组和集合框架的基础知识,数组部分涵盖了一维、二维及多维数组的声明、初始化、访问与遍历方法,以及Arrays类的常用操作,对Java数组与集合相... 目录一、Java数组基础1.1 数组结构概述1.2 一维数组1.2.1 声明与初始化1.2.2 访问

Python实现对阿里云OSS对象存储的操作详解

《Python实现对阿里云OSS对象存储的操作详解》这篇文章主要为大家详细介绍了Python实现对阿里云OSS对象存储的操作相关知识,包括连接,上传,下载,列举等功能,感兴趣的小伙伴可以了解下... 目录一、直接使用代码二、详细使用1. 环境准备2. 初始化配置3. bucket配置创建4. 文件上传到os

MySQL查询JSON数组字段包含特定字符串的方法

《MySQL查询JSON数组字段包含特定字符串的方法》在MySQL数据库中,当某个字段存储的是JSON数组,需要查询数组中包含特定字符串的记录时传统的LIKE语句无法直接使用,下面小编就为大家介绍两种... 目录问题背景解决方案对比1. 精确匹配方案(推荐)2. 模糊匹配方案参数化查询示例使用场景建议性能优

关于集合与数组转换实现方法

《关于集合与数组转换实现方法》:本文主要介绍关于集合与数组转换实现方法,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1、Arrays.asList()1.1、方法作用1.2、内部实现1.3、修改元素的影响1.4、注意事项2、list.toArray()2.1、方

SpringMVC高效获取JavaBean对象指南

《SpringMVC高效获取JavaBean对象指南》SpringMVC通过数据绑定自动将请求参数映射到JavaBean,支持表单、URL及JSON数据,需用@ModelAttribute、@Requ... 目录Spring MVC 获取 JavaBean 对象指南核心机制:数据绑定实现步骤1. 定义 Ja

Python打印对象所有属性和值的方法小结

《Python打印对象所有属性和值的方法小结》在Python开发过程中,调试代码时经常需要查看对象的当前状态,也就是对象的所有属性和对应的值,然而,Python并没有像PHP的print_r那样直接提... 目录python中打印对象所有属性和值的方法实现步骤1. 使用vars()和pprint()2. 使

MySQL JSON 查询中的对象与数组技巧及查询示例

《MySQLJSON查询中的对象与数组技巧及查询示例》MySQL中JSON对象和JSON数组查询的详细介绍及带有WHERE条件的查询示例,本文给大家介绍的非常详细,mysqljson查询示例相关知... 目录jsON 对象查询1. JSON_CONTAINS2. JSON_EXTRACT3. JSON_TA

C#之List集合去重复对象的实现方法

《C#之List集合去重复对象的实现方法》:本文主要介绍C#之List集合去重复对象的实现方法,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录C# List集合去重复对象方法1、测试数据2、测试数据3、知识点补充总结C# List集合去重复对象方法1、测试数据

Spring中管理bean对象的方式(专业级说明)

《Spring中管理bean对象的方式(专业级说明)》在Spring框架中,Bean的管理是核心功能,主要通过IoC(控制反转)容器实现,下面给大家介绍Spring中管理bean对象的方式,感兴趣的朋... 目录1.Bean的声明与注册1.1 基于XML配置1.2 基于注解(主流方式)1.3 基于Java

C++/类与对象/默认成员函数@构造函数的用法

《C++/类与对象/默认成员函数@构造函数的用法》:本文主要介绍C++/类与对象/默认成员函数@构造函数的用法,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录名词概念默认成员函数构造函数概念函数特征显示构造函数隐式构造函数总结名词概念默认构造函数:不用传参就可以