我创建一个HashMap,其键为字符串,值为ArrayList。我有另一个ArrayList说x其中我暂时存储一些东西,然后将该ArrayList x的值添加到HashMap中的某个任意键。如果x的值发生更改,则HashMap中的值也会因引用传递而更改。我该如何避免这种情况?有些文章显示错误的原因是通过引用传递,但没有文章解决此问题。
答案 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
中详细描述了防御性复制对不起,如果我的解释太长了......