Reputation: 55
I create a HashMap with key as string and value as ArrayList. I have another ArrayList say x where in I store something temporarily and then add the value of that ArrayList x to some arbitrary key in the HashMap. If the value of x changes, the value in the HashMap changes too due to pass by reference. How do I avoid this situation? There are articles showing the cause of the error being pass by reference but no article has a solution to this issue.
Upvotes: 1
Views: 6486
Reputation: 2409
To understand why this is happening, you need to understand how Java allocates objects in memory.
Every time an Object is created with the new
keyword, Java allocates 2 memory blocks:
Reference (allocated on stack) -> Object content (allocated on heap)
The actual content of the object is allocated on the heap, the reference to the object content is allocated on the stack.
Stack and heap are two different memory areas.
Stack belongs to the currently running thread. By default, it's limited to 2 MB but the stack size can be adjusted. Each thread has its own independent stack. StackOverlowError
is thrown if there is not enough memory on the stack for any thread.
Heap is a common area in memory. All threads are allocating and releasing memory in the common heap. By default, the heap is 512MB and it can be extended with Java options. If there is not enough memory on the heap, OutOfMemoryError
is thrown.
For ex:
List<String> x = new ArrayList<>();
Java will do 2 memory allocations:
1) a reference with name `x` is allocated on the stack
2) content of ArrayList is allocated on the heap
When Java is doing an assignment of one variable to another it assigns references only:
List<String> x1=x;
This operation will allocate 1 memory block:
1) a reference with name `x1` is allocated on the stack that is pointing to the content of `x`.
Now, we have:
x pointing to content of List<String>
x1 pointing to the **same** content of List<String>
Operations on x and x1 will yield the same result because they will modify the same content on the heap:
x.add("test")
x.get(0) == "test" > true
x1.get(0) == "test" > true
To avoid this situation, 2 separate references must be created:
List<String> x = new ArrayList<>();
List<String> x1 = new ArrayList<>();
Java will allocate 4 memory blocks:
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
Then:
x.add("test")
x.get(0) == "test" > true
x1.get(0) == "test" > false
As you can see, we added a value to the 'x' and that didn't affect 'x1'.
So, you need to create a new copy of x and put a copy into 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));
Then changes in x will not be reflected in the map.
One more note:
List<String> x1 = new ArrayList<>(x);
This constructor will copy all elements from 'x' to 'x1' on the heap, but 'x' and 'x1' will be separate references pointing to separate lists on the heap.
Defensive copying is perfectly described in the Effective Java book, Item 24: http://www.informit.com/articles/article.aspx?p=31551&seqNum=2
Sorry, if my explanation is too long...
Upvotes: 9
Reputation: 2492
To avoid the side effect that you are seeing, you should make a copy of your temporary ArrayList x
before adding it to the ArrayList
for your key. In the following example, new ArrayList<>(x)
will first create a copy of your temporary list x
, before adding the elements (from x
)to the existing list against your key in the map.
// 'myMap' is the name of your map, and 'myKey' is the name of your key
myMap.get("myKey").addAll(new ArrayList<>(x));
Or, if you just want to override the existing list in the map:
myMap.get("myKey").put(new ArrayList<>(x));
Upvotes: 0