Why Are All My Collection Elements the Same as the Last One I Added?
I’ve seen several programmers struggle with a similar question. They create a collection of some sort, and add items to it in a loop. When they finish the loop and try to use the collection (or list, or array, or set, or map) it looks like every single object in the collection is the same one — the last one added to the array. Usually they’ve written code that looks something like this C# example:
using System; using System.Collections.Generic; namespace Stevideter.ArrayTest { public class Program { static void Main(string[] args) { List<Person> people = new List<Person>(); Person person = new Person(); for (int i = 0; i < 3; i++) { person.Name = String.Format("Person {0}", i); person.Age = i * i; people.Add(person); } foreach (Person person1 in people) { Console.Out.WriteLine(person1); } } } class Person { private String name; public String Name { get { return name; } set { name = value; } } private int age; public int Age { get { return age; } set { age = value; } } public override string ToString() { return String.Format("{0} is {1}",name,age); } } }
What happened? Why is the output:
Person 2 is 4 Person 2 is 4 Person 2 is 4
The answer is in the mystery that is parameter passing by value.
In both Java and C#, parameters are passed by value. But what this means for objects is not what you may intuitively expect. When you pass an object reference to a parameter, the value that is passed is, in fact, a copy of the reference, not a copy of the object that is referenced. Both the original reference and the value copy point to the same object. That is why you can update the members of an object in a method that you pass it to – you have a copy of the address where that object lives on the heap.
In the code above, only one Person is created. Each trip through the loop, the members of
person are updated. When people.Add(person) is called, the reference to the object created by new Person() is copied and added to the List people.
When you change the object’s values the next time through, every reference to that object sees the changes, because they all still point to the same instance on the heap. This is why at the end, every item in the array has the most recent updates. The code, as usual, is doing exactly what you told it to do.
Instead, you need to create a new object for each object you want in the array, and add that new reference to the array. This time, let’s see it in Java:
package com.stevideter.java; import java.util.ArrayList; import java.util.List; public class ArrayTest { public static void main(String[] args) { List<Person> people = new ArrayList<Person>(); for (int i = 0; i < 3; i++) { Person person = new Person(); person.setName(String.format("Person %d", i)); person.setAge(i*i); people.add(person); } for (Person person : people) { System.out.println(person.toString()); } } } class Person { private String name; private int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return String.format("%s is %d", name, age); } }
This time we get the results we probably expected:
Person 0 is 0 Person 1 is 1 Person 2 is 4
At this point, you might be confused by the issue of scope, and feel you must declare and instantiate the object outside of the loop, afraid of losing the object when the loop ends.
What is actually lost when we leave the for loop is access to the reference person.
But the references to the heap location for the object created by each call to new Person() still exist. So the objects are still in the heap, and can still be used via the array/collection to which they were added.
I know I still get caught now and again by the implications of pass by value in Java and C#. When is the last time it surprised you?
No related posts.
Related posts brought to you by Yet Another Related Posts Plugin.
You can follow any responses to this entry through the RSS 2.0 feed. You can leave a response, or trackback from your own site.
Leave a comment.