ArrayList 修改“get”方法返回的值

5

我有以下两种涉及ArrayList get方法的情况,一种是使用自定义类,另一种是使用String类:

1. 以下是修改自定义类ArrayList元素的示例:

ArrayList<MyClass> mTmpArray1 = new ArrayList<MyClass>();
MyClass myObj1 = new MyClass(10);  
mTmpArray1.add(myObj1);

MyClass myObj2 = mTmpArray1.get(0);  
myObj2.myInt = 20;

MyClass myObj3 = mTmpArray1.get(0);  
Log.d(TAG, "Int Value:"+myObj3.myInt);    // Prints "20" 

2. 下面是修改String ArrayList元素的示例:

ArrayList<String> mTmpArray2 = new ArrayList<String>();  
mTmpArray2.add("Test_10");

String myStr1 = mTmpArray2.get(0);
myStr1 = "Test_20";

String myStr2 = mTmpArray2.get(0);
Log.d(TAG, "Str Value:"+myStr2);  // Prints "Test_10" 

在MyClass ArrayList的情况下,当我调用get并修改值时,当我再次执行get时,就会看到变化反映出来。
但是同样的方式,当我修改String ArrayList时,变化没有反映出来。
在这两种情况下,get方法有何不同?
是不是在String的情况下,String类创建了深拷贝并返回了新对象,在自定义类的情况下创建了浅拷贝?
第一种情况适用于"LinkedHashMap"、"HashMap"和"List"吗?

如果提供解释说明-1,将会非常有帮助。那我可以更新/删除问题。请提供关于-1的解释说明。 - User7723337
这两个get()没有区别:它们都返回包含在列表中的对象的引用。但是你在这两种情况下做的事情不同:1-修改所引用对象的成员(myObj2.myInt = 20;),2-尝试修改对象的引用(myStr1 = "Test_20";)。如果您尝试在第一种情况下修改引用,则会得到与第二种情况完全相同的结果。 - T.Gounelle
6个回答

3

在这两种情况下,你所做的事情是不同的。

这里你更新了一个对象的状态,因此更改会影响存储在列表中的对象:

myObj2.myInt = 20;

在这里,您正在将一个新对象分配给一个局部变量,因此列表不会受到影响:

myStr1 = "Test_20";

如果 String 是可变的,你可以通过调用某个方法来修改字符串,并且更改将会反映在存储在列表中的对象中。
myStr1.setSomething(...);

另一方面,如果在第一种情况下你改变了局部变量的值,存储在列表中的对象不会受到影响。
myObj2 = new MyClass (...);

对于myStr1 = "Test_20"的情况;如果我更改这行并添加myString.append("_somthing"),那么它也不会反映出来。 - User7723337
@A_user myString.append 不会改变 myString 的值。它会创建一个新的字符串实例并返回该实例。原始的 String 保持不变。 - Eran
如果我执行 myStr1.replace("20","10"),那么当我们执行 get 时它会反映出更改吗? - User7723337
@A_user replace 不会改变 myStr1。它会创建一个新的字符串。再次强调,字符串是不可改变的,因此对 myStr1 调用的任何方法都不会改变它。 - Eran
谢谢您的解释,所以问题在于String是不可变的,我们无法修改它的原始内容。 - User7723337

2

字符串是不可变的。你没有将新的字符串插入到数组列表中。

当你执行String myStr2 = mTmpArray2.get(0);时,即使你指向了ArrayList中的一个引用,任何尝试更改值的操作都会(由于字符串的不可变性)创建一个新的字符串(myStr2),它将不再引用ArrayList。

当你执行myStr1 = "xxx"时,你实际上没有改变ArrayList的引用,而是改变了从ArrayList获取的一个新的副本(现在称为myStr1),它具有局部作用域。

阅读更多关于字符串的内容:Java中字符串的不可变性

现在在第一个示例中,你指向了一个可变对象(你的自定义类),因此你通过引用直接更改了值。欢迎来到Java。 ;)

无关的:这段代码:MyClass myObj1 = new MyClass(10);被认为是(可以说是)不好的。最好使用一个更容易阅读的工厂模式。换句话说,带参数的公共构造函数很难阅读(例如,当我阅读你的代码时,我不知道我正在构造什么)。

一个(也许)更好的方法是:MyClass myObj = MyClass.createInstanceWithCapacity(10); //我发明了这个名字,因为我不知道你的10是什么,但看看两个,哪一个在第一眼看起来更容易理解?

免责声明:上述无关评论是我个人的意见,并非所有开发人员都会同意。 ;)


2
第一部分是错误的!在 String myStr2 = mTmpArray2.get(0); 中没有复制,你引用了列表中的 String 对象。@Eran 的解释是正确的。 - T.Gounelle
那好了,我修改了措辞以满足你的要求。 - Martin Marconcini
1
这与我的幸福无关,也不是措辞问题。看看代码,你就会明白在执行 List#get(int) 时没有任何对象的副本,即使对于一个不可变的 String 也是如此。 - T.Gounelle
同意Abbé Résina和@MartínMarconcini的观点,对于你回答中的“无关”部分,我认为在简单情况下使用工厂模式会使代码更加复杂。例如,“new MyClass(10)”中的10是传递给MyClass构造函数的值,它将把这个值赋给它的成员变量“myInt”,并创建一个新实例,然后将其添加到列表中。此外,我提供的代码仅用于解释问题陈述。 - User7723337
MyClass(10) 相对于显式方法来说,更容易理解(无需查找那个10是什么、它在哪里以及它的作用)。您同意的是什么?由于您没有 Java 字符串的基本理解,因此我认为您对 Java 的经验很少,因此可以轻松地假设您没有足够的经验来理解清晰代码与“简单事物”的好处。无论如何,我提供了免费的自愿建议,而不是您必须要做的事情。老实说,在您的代码中,MyClass(10) 是不必要的,只会增加混乱,因为它是无关紧要的。 - Martin Marconcini
显示剩余3条评论

1

字符串有一个非常好的属性叫做“不可变性”。

这意味着字符串是不可变的(无法更改),当我们创建/尝试引用旧字符串时,会创建一个新实例字符串。我们所做的任何更改都保存在新实例中,并且不会影响旧字符串。

例如:

String s = "Old String";
System.out.println("Old String : "+s); // output : Old String

String s2 = s;
s2 = s2.concat(" made New");
System.out.println("New String : "+s2); // output : Old String made New
System.out.println("Old String is not changed : "+s); // output : Old String

所以在s2 = s的情况下,你的意思是s2指向s并且我们正在修改s,那么为什么在ArrayList的情况下这不成立呢? - User7723337
如果s2 = s,则s2是对s的引用,但是当您在s2中进行更改时,由于字符串的不可变性,更改将反映在新的结果字符串对象中。 s将保持不变 - Kushal
但是你的输出显示s将输出"Old String made New"。所以我们已经改变了s的值,这不应该发生。 - User7723337
我已经纠正了我的错误。谢谢你告诉我这个错误。 - Kushal
明白了,感谢提供信息。 - User7723337

0
这两个“get”调用之间没有区别。区别在于ArrayList所持有的类型以及您对“get”方法返回的引用所做的操作。
在您的第一个示例中,您执行以下操作:
MyClass myObj2 = mTmpArray1.get(0);  
myObj2.myInt = 20;

在这里,您正在获取ArrayList中位置0的MyClass实例的引用,并修改此实例中的字段。

在您的第二个示例中,您执行以下操作:

String myStr1 = mTmpArray2.get(0);
myStr1 = "Test_20";

这里,您正在获取数组列表中String实例的引用,然后将myStr1引用指向您创建的不同字符串("Test_20")。这就好像你在第一个示例的第2行中写入了myObj2 = new MyClass(20);

因此,简而言之,在第一个示例中,您通过从已获得的引用更改该对象中的字段来访问对象内部的字段。在第二个示例中,您仅仅更改了引用以指向另一个String。

我还应该提到,在Java中,字符串是不可变的,这意味着一旦它们被创建,就无法更改。


0

在您的第二个示例中,您根本不需要更改列表。

在第一个示例中,您正在执行以下操作:

  1. 从列表中获取第一个对象并将其存储在名为“myObj2”的变量中
  2. 通过设置此对象的int值来修改存储在变量“myObj2”中的对象

但是您的第二个示例完全不同:

String myStr1 = mTmpArray2.get(0);
myStr1 = "Test_20";

让我来翻译一下:

  1. 从列表中获取第一个元素并将其存储在名为'myStr1'的变量中。
  2. 将变量'myStr1'的值设置为"Test_20"

因此,在第一种情况下,您正在修改存储在列表中的对象的变量。在第二种情况下,您正在检索存储在列表中的对象 - 然后重新使用您存储那个检索到的对象的变量,并将其用于某些新事物 - 但这当然不会改变原始列表。

要修改类型为字符串的列表,您需要使用set(x,“Test_20”)。


0

String是一个不可变的类。像这样的一行

myStr1 = "Test_20";

不会改变String对象myStr1指向的值。相反,会创建一个新的String,并修改myStr1以指向新的String。原始的String保持不变,可以从ArrayList中检索出来。
你的MyClass对象明显是可变的。只创建了一个实例,并且通过赋值改变其状态。
myObj2.myInt = 20;

因此,当从ArrayList检索此对象时,将看到其新状态。

虽然正确,但String是不可变的事实在这里实际上并不重要。如果他用其他对象并编写了mySomeOtherObject1 = new MySomeOtherObject(),整个过程将完全以相同的方式工作。 - Florian Schaetz
同意,但是对于其他字符串修改操作,比如 myStr1.replace("20","10") 也会发生同样的情况。 - Neil Masson
同意。正如Eran已经提到的,如果String不是不可变的,那么像myStr1.setValue("...")这样的东西就可以工作了。 - Florian Schaetz

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接