Map复制给新Map时,用 “=、clone、还是putAll”?论Map的深复制和浅复制

本文讲述了在Java中使用`=`,`putAll()`,和`clone()`方法复制Map时遇到的深浅复制问题,以及实际操作中的局限性,重点分析了这些方法在处理引用类型数据时的不足,并提供了测试用例和源码解析。
摘要由CSDN通过智能技术生成
    • 1. “=”赋值
  • 2. 使用.putAll()方法

  • 3. 使用.clone()方法

  • 测试用例

  • 测试用例源码

使用场景

===================================================================

在我们最初使用map复制开发业务代码时,通常会踩到深浅复制(拷贝)这个坑里,比如我,在Map复制时

(如:Map<String, String> new_Map = old_Map) 出现过以下两类问题:

1.使用Map<String, String> new_Map = old_Map 操作,当修改new_Map属性后,old_Map属性也跟着变了,但我并没有修改过old_Map

2.由于Map中的value值不仅有基本数据类型,还有引用数据类型,所以当我修改引用类型属性后,new_Map和old_Map的引用变量值都发生变化;(如你的value都是基本类型,就不涉及深浅拷贝的问题)

尝试过的办法

=====================================================================

1. “=”赋值


新建一个Map,然后使用“=”直接赋值,这样只是复制了old_Map的引用,和old_Map仍使用同一个内存区域,所以,在修改new_Map的时候,old_Map的值同样会发生变化。

<Map<String, String> new_Map = old_Map>

上述的办法不行,使用Map本身提供的方法,网上大都说putAll()和clone()方法就是深拷贝,但是实际使用后,发现前后Map中的引用对象还是都被改变了;这里就是开头说到的,这两个方法只能修改基本数据类型的,如果是引用类型不行,这两个方法是浅拷贝!

来,让我们一起跟一下源码↓↓↓

2. 使用.putAll()方法


创建一个新的Map结构,使用putAll()方法把原先的Map添加到新的Map中,但是发现修改了副本的Map之后,原先的Map中数据也被修改了;(源码如下)

public void putAll(Map<? extends K, ? extends V> m) {

putMapEntries(m, true); // 调用了putMapEntries方法

}

final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {

int s = m.size();

if (s > 0) {

if (table == null) { // pre-size

float ft = ((float)s / loadFactor) + 1.0F;

int t = ((ft < (float)MAXIMUM_CAPACITY) ?

(int)ft : MAXIMUM_CAPACITY);

if (t > threshold)

threshold = tableSizeFor(t);

}

else if (s > threshold)

resize();

for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {

K key = e.getKey();

V value = e.getValue();

putVal(hash(key), key, value, false, evict); // 循环调用了value,但value中的引用对象指针并没有改变。

// 扩展:map.put(“key”,“value”)的put()也是调用了putVal()方法

}

}

}

3. 使用.clone()方法


HashMap自带了一个clone()方法,但是,它的源码中注释说明了也只是一种浅复制(拷贝):(源码如下)

@Override

public Object clone() {

HashMap<K,V> result;

try {

result = (HashMap<K,V>)super.clone();

} catch (CloneNotSupportedException e) {

// this shouldn’t happen, since we are Cloneable

throw new InternalError(e);

}

result.reinitialize(); // 清空map

result.putMapEntries(this, false); // 可见,和putAll调用了同一个接口,

return result;

}

final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {

int s = m.size();

if (s > 0) {

if (table == null) { // pre-size

float ft = ((float)s / loadFactor) + 1.0F;

int t = ((ft < (float)MAXIMUM_CAPACITY) ?

(int)ft : MAXIMUM_CAPACITY);

if (t > threshold)

threshold = tableSizeFor(t);

}

else if (s > threshold)

resize();

for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {

K key = e.getKey();

V value = e.getValue();

putVal(hash(key), key, value, false, evict); // 同上,循环调用了“value”,value中的引用对象指针并没有改变

}

}

}

测试用例

===================================================================

List list = new ArrayList();

list.add(100);

list.add(200);

HashMap<String,Object> old_map = new HashMap<String,Object>();

old_map.put(“name”, “蔡虚坤”);//放基本类型数据

old_map.put(“list”, list);//放对象

HashMap<String,Object> new_map = new HashMap<String,Object>();

new_map.putAll(old_map);

System.out.println(“----基础数据展示-----”);

System.out.println("old: " + old_map);

System.out.println("new: " + new_map);

System.out.println(“----更改基本数据类型的数据-----”);

old_map.put(“name”, “娘炮”);

System.out.println("old: " + old_map);

System.out.println("new: " + new_map);

System.out.println(“----更改引用类型的数据-----”);

list.add(300);

System.out.println("old: " + old_map);

System.out.println("new: " + new_map);

System.out.println(“----使用序列化进行深拷贝 自定义Clone方法-----”);

new_map = myClone(old_map); // myClone() 方法源码在下方 ↓↓

list.add(400);

System.out.println("old: " + old_map);

System.out.println("new: " + new_map);

输出结果:

Connected to the target VM, address: ‘127.0.0.1:58242’, transport: ‘socket’

----基础数据展示-----

old: {name=蔡虚坤, list=[100, 200]}

new: {name=蔡虚坤, list=[100, 200]}

----更改基本数据类型的数据-----

old: {name=娘炮, list=[100, 200]}

new: {name=蔡虚坤, list=[100, 200]}

----更改引用类型的数据-----

old: {name=娘炮, list=[100, 200, 300]}

new: {name=蔡虚坤, list=[100, 200, 300]}

----使用序列化进行深拷贝-----

old: {name=娘炮, list=[100, 200, 300, 400]}

new: {name=娘炮, list=[100, 200, 300]}

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
42)]

[外链图片转存中…(img-2kWQP6NX-1714875684743)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

  • 17
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值