Java中如何克隆集合——ArrayList和HashSet深拷贝

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

编程人员经常误用各个集合类提供的拷贝构造函数作为克隆ListSetArrayListHashSet或者其他集合实现的方法。需要记住的是,Java集合的拷贝构造函数只提供浅拷贝而不是深拷贝,这意味着存储在原始List和克隆List中的对象是相同的,指向Java堆内存中相同的位置。增加了这个误解的原因之一是对于不可变对象集合的浅克隆。由于不可变性,即使两个集合指向相同的对象是可以的。字符串池包含的字符串就是这种情况,更改一个不会影响到另一个。使用ArrayList的拷贝构造函数创建雇员List的拷贝时就会出现问题,Employee类不是不可变的。在这种情况下,如果原始集合修改了雇员信息,这个变化也将反映到克隆集合。同样如果克隆集合雇员信息发生变化,原始集合也会被更改。绝大多数情况下,这种变化不是我们所希望的,克隆对象应该与原始对象独立。解决这个问题的方法是深克隆集合,深克隆将递归克隆对象直到基本数据类型或者不可变类。本文将了解一下深拷贝ArrayList或者HashSet等集合类的一种方法。如果你了解深拷贝与浅拷贝之间的区别,那么理解集合深克隆的方法就会很简单。

Java集合的深克隆

下面例子有一个Employee集合,Employee是可变对象,成员变量namedesignation。它们存储在HashSet中。使用java.util.Collection接口的addAll()方法创建集合拷贝。然后修改存储在原始集合每个Employee对象的designation值。理想情况下这个改变不会影响克隆集合,因为克隆集合和原始集合应该相互独立,但是克隆集合也被改变了。修正这个问题的方法是对存储在Collection类中的元素深克隆。

package javaBasic;import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;/*** Java program to demonstrate copy constructor of Collection provides shallow* copy and techniques to deep clone Collection by iterating over them.* * @author http://javarevisited.blogspot.com*/
public class CollectionCloningTest {public static void main(String args[]) {// deep cloning Collection in JavaCollection<Employee> org = new HashSet<Employee>();org.add(new Employee("Joe", "Manager"));org.add(new Employee("Tim", "Developer"));org.add(new Employee("Frank", "Developer"));// creating copy of Collection using copy constructorCollection<Employee> copy = new HashSet<Employee>(org);System.out.println("Original Collection {} " + org);System.out.println("Copy of Collection {} " + copy);Iterator<Employee> itr = org.iterator();while (itr.hasNext()) {itr.next().setDesignation("staff");}System.out.println("Original Collection after modification {} " + org);System.out.println("Copy of Collection without modification {} " + copy);// deep Cloning List in Java}
}class Employee {private String name;private String designation;public Employee(String name, String designation) {this.name = name;this.designation = designation;}public String getDesignation() {return designation;}public void setDesignation(String designation) {this.designation = designation;}public String getName() {return name;}public void setName(String name) {this.name = name;}@Overridepublic String toString() {return String.format("%s: %s", name, designation);}
}

输出:

1
2
3
4
- Original Collection [Joe: Manager, Frank: Developer, Tim: Developer]
- Copy of Collection [Joe: Manager, Frank: Developer, Tim: Developer]
- Original Collection after modification [Joe: staff, Frank: staff, Tim: staff]
- Copy of Collection without modification [Joe: staff, Frank: staff, Tim: staff]

可以看到改变原始CollectionEmployee对象(改变designation为”staff“)在克隆集合中也有所反映,因为克隆是浅拷贝,指向堆中相同的Employee对象。为了修正这个问题,需要遍历集合,深克隆Employee对象,在这之前,要重写Employee对象的clone方法。

1)Employee实现Cloneable接口
2)为Employee类增加下面的clone()方法

1
2
3
4
5
6
7
8
9
10
11
12
@Override
     protected Employee clone() {
         Employee clone = null ;
         try {
             clone = (Employee) super .clone();
         } catch (CloneNotSupportedException e){
             throw new RuntimeException(e); // won't happen
         }
          
         return clone;
     }

3)不使用拷贝构造函数,使用下面的代码来深拷贝集合

1
2
3
4
5
6
Collection<Employee> copy = new HashSet<Employee>(org.size());
Iterator<Employee> iterator = org.iterator();
while (iterator.hasNext()){
     copy.add(iterator.next().clone());
}

Code

package javaBasic;import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;/*** Java program to demonstrate copy constructor of Collection provides shallow* copy and techniques to deep clone Collection by iterating over them.* * @author http://javarevisited.blogspot.com*/
public class CollectionCloningTest {public static void main(String args[]) {// deep cloning Collection in JavaCollection<Employee> org = new HashSet<Employee>();org.add(new Employee("Joe", "Manager"));org.add(new Employee("Tim", "Developer"));org.add(new Employee("Frank", "Developer"));// creating copy of Collection using copy constructor// Collection<Employee> copy = new HashSet<Employee>(org);/*** 不使用拷贝构造函数,使用下面的代码来深拷贝集合*/Collection<Employee> copy = new HashSet<Employee>(org.size());Iterator<Employee> iterator = org.iterator();while (iterator.hasNext()) {copy.add(iterator.next().clone());}System.out.println("Original Collection {} " + org);System.out.println("Copy of Collection {} " + copy);Iterator<Employee> itr = org.iterator();while (itr.hasNext()) {itr.next().setDesignation("staff");}System.out.println("Original Collection after modification {} " + org);System.out.println("Copy of Collection without modification {} " + copy);// deep Cloning List in Java}
}class Employee implements Cloneable {private String name;private String designation;public Employee(String name, String designation) {this.name = name;this.designation = designation;}public String getDesignation() {return designation;}public void setDesignation(String designation) {this.designation = designation;}public String getName() {return name;}public void setName(String name) {this.name = name;}@Overridepublic String toString() {return String.format("%s: %s", name, designation);}@Overrideprotected Employee clone() {Employee clone = null;try {clone = (Employee) super.clone();} catch (CloneNotSupportedException e) {throw new RuntimeException(e); // won't happen}return clone;}
}

4)运行相同的代码更改原始集合,克隆集合不会也被更改。

1
2
- Original Collection after modification  [Joe: staff, Tim: staff, Frank: staff]
- Copy of Collection without modification [Frank: Developer, Joe: Manager, Tim: Developer]

可以看到克隆集合和原始集合相互独立,它们指向不同的对象。

这就是Java中如何克隆集合的内容。现在我们知道拷贝构造函数或者ListSet等各种集合类的addAll()方法仅仅创建了集合的浅拷贝,而且原始集合和克隆集合指向相同的对象。为避免这个问题,应该深克隆集合,遍历集合克隆每个元素。尽管这要求集合中的对象必须支持深克隆操作。


转载于:https://my.oschina.net/u/1412027/blog/223854

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/401849.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C++基础知识(五)—— 基本输入输出

控制台(console)是电脑的最基本交互接口&#xff0c;通常包括键盘(keyboard)和屏幕(screen)。键盘通常为标准输入设备&#xff0c;而 屏幕为标准输出设备。 在C的iostream函数库中&#xff0c;一个程序的标准输入输出操作依靠两种数据流&#xff1a;cin 给输入使用和cout给输出…

PHP之preg_replace()与ereg_replace()正则匹配比较讲解

<?php//preg_replace()和ereg_replace()函数的使用的比较// -------preg_replace()--------------------------//1.进行字符串的查找的替换 $str "daoyu shi ge hao hai zi 5555"; $pattern "/\s/"; //如果将…

C++ 控制结构和函数(一) —— 控制结构

一个程序的语句往往并不仅限于线性顺序结构。在程序的执行过程中它可能被分成两支执行&#xff0c;可能重复某些语句&#xff0c;也可能根据一些判断结果而执行不同的语句。因此C 提供一些控制结构语句 (control structures) 来实现这些执行顺序。 为了介绍程序的执行顺序&…

如何安装Favicon

如何安装Favicon favicon.ico图像放在根目录下(也可以是其他目录)在页面源文件的<head></head>标签之间插入 <link rel"shortcut icon" href" /favicon.ico" /> 最后形成: <head> ... <link rel"shortcut icon" hre…

C++ 控制结构和函数(二) —— 函数I(Functions I)

通过使用函数(functions)我们可以把我们的程序以更模块化的形式组织起来&#xff0c;从而利用C所能提供的所有结构化编程的潜力。 一个函数(function)是一个可以从程序其它地方调用执行的语句块。以下是它的格式&#xff1a; type name ( argument1, argument2, ...) statement…

条件概率的几何解释 由定义计算条件概率 由条件概率公式计算条件概率

A发生&#xff0c;则去掉圈A以外的区域&#xff0c;形成新的样本空间 &#xff08;如果是概率质量函数&#xff0c;则称为归一化&#xff09;然后在A发生的前提下&#xff0c;B的概率为 圈A与圈B的公共区域/圈A 这就是条件概率的几何解释~~~ 甲乙两人各抛一个骰子&#xff0c…

C++ 控制结构和函数(三)—— 函数II(Functions II)

参数按数值传递和按地址传递(Arguments passed by value and by reference) 到目前为止&#xff0c;我们看到的所有函数中&#xff0c;传递到函数中的参数全部是按数值传递的(by value)。也就是说&#xff0c;当我们调用一个带有参数的函数时&#xff0c;我们传递到函数中的是变…

oracle XMLType字段使用方法

2019独角兽企业重金招聘Python工程师标准>>> 刚才研究了一下XMLType字段使用方法 &#xff0c;现在给大家介绍一下。 主要是新增、查询、修改XMLType字段 表结构&#xff1a; 建表sql&#xff1a; -- Create table create table T_BOOK ( ID VARCHAR2(32)…

C++ 高级数据类型(一)—— 数组

数组(Arrays) 是在内存中连续存储的一组同种数据类型的元素&#xff08;变量&#xff09;&#xff0c;每一数组有一个唯一名称&#xff0c;通过在名称后面加索引&#xff08;index&#xff09;的方式可以引用它的每一个元素。 也就是说&#xff0c;例如我们有5个整型数值需要存…

DataUml Design 介绍8-DataUML 1.2版本正式发布

为什么80%的码农都做不了架构师&#xff1f;>>> DataUML 1.2版本在软件架构上有了很大的变化&#xff0c;目前DataUML支持Access、SQLite、MY SQL 、ORACLE、MS SERVER2000、MS SERVER2005、MS SERVER2008数据库。 下载 主要更新内容如下&#xff1a;  1、支持S…

C++ 高级数据类型(二)—— 字符序列

前面基础知识部分讲C变量类型的时候&#xff0c;我们已经提到过C的标准函数库提供了一个string类来支持对字符串的操作。然而&#xff0c;字符串实际就是一串连续的字符序列&#xff0c;所以我们也可以用简单的字符数组来表示它。 例如&#xff0c;下面这个数组: char jenny …

转: seajs手册与文档之 -- 模块标识

目录 模块标识 相对标识顶级标识普通路径文件后缀的提示模块标识 模块标识是一个字符串&#xff0c;用来标识模块。在 require、 require.async 等加载函数中&#xff0c;第一个参数都是模块标识。define 函数的 dependencies 参数也是由模块标识组成。 SeaJS 中的模块标识是 C…

C++ 高级数据类型(三)—— 指针

我们已经明白变量其实是可以由标识来存取的内存单元。但这些变量实际上是存储在内存中具体的位置上的。对我们的程序来说&#xff0c;计算机内存只是一串连续的单字节单元(1byte cell)&#xff0c;即最小数据单位&#xff0c;每一个单元有一个唯一地址。 计算机内存就好像城市中…

C++ 高级数据类型(四)—— 动态内存分配

到目前为止&#xff0c;我们的程序中我们只用了声明变量、数组和其他对象&#xff08;objects&#xff09;所必需的内存空间&#xff0c;这些内存空间的大小都在程序执行之前就已经确定了。但如果我们需要内存大小为一个变量&#xff0c;其数值只有在程序运行时 (runtime)才能确…

Linux下查看文件和文件夹大小的df和du命令(链接)

http://www.yayu.org/look.php?id162转载于:https://www.cnblogs.com/tyhmj/p/3682480.html

C++ 高级数据类型(五)—— 数据结构

一个数据结构是组合到同一定义下的一组不同类型的数据&#xff0c;各个数据类型的长度可能不同。它的形式是&#xff1a; struct model_name {type1 element1;type2 element2;type3 element3;..} object_name;这里model_name 是一个这个结构类型的模块名称。object_name 为可选…

ubuntu 手动安装mysql

申请了一台云主机&#xff0c;需要手动安装所有环境&#xff0c;今天将mysql安装过程记下。 安装mysqla. 下载不了gcc, 需要先运行apt-get updateb. cmake报错&#xff0c;每次要先删除cmakeCache.txt&#xff0c;再重新跑cmakec. apt-get install ncurses找不到对应包&#xf…

C++ 高级数据类型(六)—— 自定义数据类型

前面我们已经看到过一种用户&#xff08;程序员&#xff09;定义的数据类型&#xff1a;结构。除此之外&#xff0c;还有一些其它类型的用户自定义数据类型&#xff1a; 定义自己的数据类型 (typedef) C 允许我们在现有数据类型的基础上定义我们自己的数据类型。我们将用关键字…

JSF 2.0/2.1 生命周期简介

2019独角兽企业重金招聘Python工程师标准>>> 标准的生命周期划分为六个阶段&#xff1a;恢复视图、应用请求值、验证、更新模型值、调用应用程序、渲染响应&#xff0c;每一个阶段都可以直接跳转到最后一个阶段或者结束。 转载于:https://my.oschina.net/koulikoro/…

C++ 面向对象(一)—— 类(Classes)

类(class)是一种将数据和函数组织在同一个结构里的逻辑方法。定义类的关键字为class &#xff0c;其功能与C语言中的struct类似&#xff0c;不同之处是class可以包含函数&#xff0c;而不像struct只能包含数据元素。 类定义的形式是&#xff1a; [cpp] view plaincopy class cl…