为什么可以在 for...each 循环中修改自定义对象的 ArrayList

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

我们有一个简单的自定义对象:

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+"]" ;  
    }
}

并且,我们尝试使用 for...each 循环修改此类对象的 ArrayList。当不能以这种方式修改 String 对象或 Integer 对象的 ArrayList 时,为什么列表中的对象会被修改?

我的测试代码:

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);

}
}

运行 这将产生以下输出:

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]

那么,为什么我可以修改 objL 而不是 sList 或 numList?

因为重新分配和变异是两个不同的东西。

s = "asdf" 将改变 s 所指的内容。它曾经包含对 sList 成员的引用,现在它引用 "asdf"。变更与sList.

成员无关

inumList 相似,但不完全相同。 numList 包含 Integer 个对象,从 135 自动装箱... for 会将 Integer 对象值分配给 i.如果您随后将 i 的值更改为(自动装箱)Integer(8),它也不会影响 numList

然而,使用 coobjL,你做的事情就完全不同了。您没有将 co 重新分配给另一个对象(这不会影响 objL 中的对象),而是选择调用 co 上的方法,然后碰巧改变了它们的状态。请注意,这里您不是修改objL,您修改的是它包含的对象。

关键是 icos 不是相应列表的元素。它们包含值可能是列表的元素(但同样在自动拆箱的情况下,这也不成立)。

您实际上并没有修改字符串和整数列表中的项目,您只是在影响循环中局部变量的新值,而这个新值只是在循环的下一步中丢失。

在您的自定义对象上,您可以获得对该对象的引用并调用修改它的方法。如果有这样的方法,您可以对 String 或 Integers 执行相同的操作。但不幸的是(或者不是?)String class 中没有方法来修改它的值。

我觉得可以这么说:

字符串列表:

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

现在你的 for-each 做了一个 var s 来保存引用 001:

s -> memory addr 001 -> "foo"

当你做 s="asdf":

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

因此没有更改带有 addr 001 的字符串,而是创建了一个新的字符串对象 00x。现在 s 包含这个新引用,但是旧引用 001 仍然由 list[0] 持有,因此列表中的字符串没有改变。您刚刚更改了 s 持有的引用。与整数相同,i=8 将使 i 保持新的引用。

但是对象列表就不一样了。您使用相同的对象引用来执行 setter() 因此您更改了对象,列表也已更改。