Kinnison84
Kinnison84

Reputation: 176

Why is it possible to modify an ArrayList of Custom Objects in a for...each loop

We have a simple Custom Object :

public class CustomObject {
    public CustomObject(int myIntNumber, String myString) {
    this.myIntNumber = myIntNumber ;
    this.myString = myString;    
    }

    private int myIntNumber;
    private String myString;

    public void setMyIntNumber(int myIntNumber) {
    this.myIntNumber = myIntNumber;
}

public int getMyIntNumber() {
    return myIntNumber;
}

public void setMyString(String myString) {
    this.myString = myString;
}

public String getMyString() {
    return myString;
}


    public String toString() {
        return "CustomObject [" + String.valueOf(myIntNumber)  + ", "+ myString+"]" ;  
    }
}

and, we try to modify an ArrayList of such objects with a for...each loop. Why do the objects in the list get modified, when an ArrayList of String objects or of Integer objects cannot be modified in this way ?

My test code:

import java.util.ArrayList;

public class TestTraveringListModification {

    public static void main(String[] args) {

    ArrayList<String> sList  = new ArrayList<String>();
    sList.add("String a");
    sList.add("String b");
    sList.add("C");
    sList.add("D");
    sList.add("String f");
    sList.add("String e");

    System.out.println("Before: "+sList);
    for (String s : sList) {
        s="asdf" ;

    }

    System.out.println("After: "+ sList);

    ArrayList<CustomObject> objL = new ArrayList<CustomObject> () ;
    objL.add(new CustomObject (1, "test") );
    objL.add(new CustomObject (2, "jim") );
    objL.add(new CustomObject (20, "dec") );
    objL.add(new CustomObject (60, "what") );
  System.out.println("before: "+ objL );
    for(CustomObject co : objL ){    
        co.setMyIntNumber(-1);
        co.setMyString("modified String");
    }
    System.out.println("after: "+objL);


    ArrayList<Integer> numList = new ArrayList<Integer>(); 

    numList.add(1);
    numList.add(3);
    numList.add(5);
    numList.add(67);
    numList.add(9598);

    System.out.println("before: "+ numList);

    for (Integer i : numList){
        i = 8; 
    }
    System.out.println("after: "+ numList);

}
}

Running this will produce the following output:

Before: [String a, String b, C, D, String f, String e]

After: [String a, String b, C, D, String f, String e]

before: [CustomObject [1, test], CustomObject [2, jim], CustomObject [20, dec], CustomObject [60, what]]

after: [CustomObject [-1, modified String], CustomObject [-1, modified String], CustomObject [-1, modified String], CustomObject [-1, modified String]]

before: [1, 3, 5, 67, 9598]

So, why is it that I can modify objL and not sList or numList ?

Upvotes: 2

Views: 897

Answers (3)

Amadan
Amadan

Reputation: 198388

Because reassignment and mutation are two different things.

s = "asdf" will change what s is refering to. It used to contain a reference to a member of sList, now it refers to "asdf". The change has nothing to do with the member of sList.

Similar with i and numList, though not completely exactly same. numList contains Integer objects, autoboxed from 1, 3, 5... for will assign the Integer objects value to i. If you then change the value of i to (autoboxed) Integer(8), it also doesn't affect numList any.

However, with co and objL, you do a very different thing. Instead of reassigning co to another object (which would not affect objects in objL), you chose to invoke methods on co, which then happened to change their state. Note that here you are not modifying objL, you're modifying the objects it contains.

The key insight is that i, co and s are not elements of the respective lists. They contain values that might be elements of the lists (but again in case of autounboxing, this too does not hold).

Upvotes: 6

Kent
Kent

Reputation: 195139

I think this can be put in this way:

The String list:

list[0] -> memory addr 001 -> "foo"
list[1] -> memory addr 002 -> "bar"
list[2] -> memory addr 003 -> "blah"

Now your for-each made a var s to hold the reference 001:

s -> memory addr 001 -> "foo"

When you do s="asdf":

s -> memory addr 00x -> "asdf"

So the string with addr 001 was not changed, instead, a new string object 00x was created. Now the s contains this new reference, however the old reference 001 was still held by the list[0], thus the strings in the list were not changed. You have just changed the references held by s. Same for the Integer, i=8 will make i hold a new reference.

However for the object list, it is different. You use the same object reference to do setter() therefore you changed the object, the list was changed as well.

Upvotes: 2

StephaneM
StephaneM

Reputation: 4899

You are actually not modifying items from your String and Integer lists, you are just affecting new values to the local variables in your loops, and this new value is just lost on the next step of the loop.

On you custom object you get the reference to the object and call a method which modifies it. You could do the same on String or Integers if there were such methods. But unfortunately (or not?) there is no method in the String class to modify its value.

Upvotes: 1

Related Questions