HashMap的解决方案在Java中通过引用传递

时间:2018-06-13 19:43:07

标签: java

我创建一个HashMap,其键为字符串,值为ArrayList。我有另一个ArrayList说x其中我暂时存储一些东西,然后将该ArrayList x的值添加到HashMap中的某个任意键。如果x的值发生更改,则HashMap中的值也会因引用传递而更改。我该如何避免这种情况?有些文章显示错误的原因是通过引用传递,但没有文章解决此问题。

2 个答案:

答案 0 :(得分:0)

为避免您所看到的副作用,您应先复制临时ArrayList x,然后再将其添加到密钥的ArrayList。在以下示例中,new ArrayList<>(x)将首先创建临时列表x的副本,然后根据地图中的密钥将元素(从x)添加到现有列表。

// 'myMap' is the name of your map, and 'myKey' is the name of your key myMap.get("myKey").addAll(new ArrayList<>(x));

或者,如果您只想覆盖地图中的现有列表:

myMap.get("myKey").put(new ArrayList<>(x));

答案 1 :(得分:0)

要理解为什么会发生这种情况,您需要了解Java如何在内存中分配对象。

每次使用new关键字创建Object时,Java都会分配2个内存块:

Reference (allocated on stack) -> Object content (allocated on heap)

对象的实际内容在堆上分配,对象内容的引用在堆栈上分配。

堆栈和堆是两个不同的内存区域。

Stack属于当前正在运行的线程。默认情况下,它限制为2 MB,但可以调整堆栈大小。每个线程都有自己独立的堆栈。如果任何线程的堆栈内存不足,则抛出StackOverlowError

堆是记忆中的常见区域。所有线程都在公共堆中分配和释放内存。默认情况下,堆为512MB,可以使用Java选项进行扩展。如果堆上没有足够的内存,则抛出OutOfMemoryError

例如:

List<String> x = new ArrayList<>();

Java将进行2次内存分配:

1) a reference with name `x` is allocated on the stack
2) content of ArrayList is allocated on the heap

当Java将一个变量赋值给另一个变量时,它只分配引用:

List<String> x1=x;

此操作将分配1个内存块:

1) a reference with name `x1` is allocated on the stack that is pointing to the content of `x`.

现在,我们有:

x pointing to content of List<String>
x1 pointing to the **same** content of List<String> 

x和x1上的操作将产生相同的结果,因为它们将修改堆上的相同内容:

x.add("test")
x.get(0) == "test" > true
x1.get(0) == "test" > true

为避免这种情况,必须创建2个单独的引用:

List<String> x = new ArrayList<>();
List<String> x1 = new ArrayList<>();

Java将分配4个内存块:

1) reference 'x' on the stack
2) content of 'x' on the heap
3) reference 'x1' on the stack
4) content of 'x1' on the heap

然后:

x.add("test")
x.get(0) == "test" > true
x1.get(0) == "test" > false

如您所见,我们在'x'中添加了一个值,但不会影响'x1'。

因此,您需要创建一个x的新副本并将副本放入HashMap。

List<String> x = new ArrayList<>();
Map<String, List<String>> map = new HashMap<>();

List<String> x1 = new ArrayList<>(x);

map.put("test", x1);

OR

List<String> x = new ArrayList<>();
Map<String, List<String>> map = new HashMap<>();
map.put("test", new ArrayList<>(x));

然后x中的变化不会反映在地图中。

还有一点需要注意:

List<String> x1 = new ArrayList<>(x);

这个构造函数会将堆上的所有元素从'x'复制到'x1',但'x'和'x1'将是指向堆上单独列表的单独引用。

“有效Java”一书第24项:http://www.informit.com/articles/article.aspx?p=31551&seqNum=2

中详细描述了防御性复制

对不起,如果我的解释太长了......