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

使用场景

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

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

1.使用Map 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的值同样会发生变化。

new_Map = old_Map>

1

上述的办法不行,使用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()方法

}

}

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

3. 使用.clone()方法

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

@Override

public Object clone() {

HashMap result;

try {

result = (HashMap)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中的引用对象指针并没有改变

}

}

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

测试用例

List list = new ArrayList();

list.add(100);

list.add(200);

HashMap old_map = new HashMap();

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

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

HashMap new_map = new HashMap();

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]}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

#最上面的两条是原始数据,使用了putAll方法拷贝了一个新的new_map对象,

#中间两条,是修改old_map对象的基本数据类型的时候,并没有影响到new_map对象。

#但是看倒数第二组,更改引用数据类型的时候,发现new_map的值也变化了,所以putAll并没有对old_map产生深拷贝。

#最后面是使用序列化的方式,发现,更改引用类型的数据的时候,new_map对象并没有发生变化,所以产生了深拷贝。(下方提供自定义clone方法源码)

#上述的工具类,可以实现对象的深拷贝,不仅限于HashMap,前提是实现了Serlizeable接口。

测试用例源码

package com.softsec.demo;

import java.io.*;

import java.util.*;

public class demoMap implements Cloneable{

public static void main(String[] srag) {

List list = new ArrayList();

list.add(100);

list.add(200);

HashMap old_map = new HashMap();

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

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

HashMap new_map = new HashMap();

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);

list.add(400);

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

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

}

/**

* 自定义clone方法(对象必须是实现了Serializable接口)

*

*/

public static T myClone(T obj) {

T clonedObj = null;

try {

ByteArrayOutputStream baos = new ByteArrayOutputStream();

ObjectOutputStream oos = new ObjectOutputStream(baos);

oos.writeObject(obj);

oos.close();

ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());

ObjectInputStream ois = new ObjectInputStream(bais);

clonedObj = (T) ois.readObject();

ois.close();

} catch (Exception e) {

e.printStackTrace();

}

return clonedObj;

}

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值