【问题标题】:ArrayList modifying value returned by "get" method [duplicate]ArrayList修改“get”方法返回的值[重复]
【发布时间】:2015-03-18 09:57:43
【问题描述】:

我有以下两种与ArrayListget方法相关的情况,一种是自定义类,一种是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类创建深拷贝并返回新对象,而在Custom类的情况下创建浅拷贝?

第一种场景适用于“LinkedHashMap”、“HashMap”和“List”?

【问题讨论】:

  • 如果为 -1 提供了解释,将会有很大帮助。因此,我可以更新/删除问题。请提供-1的解释。
  • 这两个get() 没有区别:它们都返回对列表中包含的对象的引用。但是在这两种情况下你都不会做同样的事情:1- 你修改了被引用对象的成员 (myObj2.myInt = 20;),2- 你尝试修改对对象的引用 (myStr1 = "Test_20";)。如果您尝试在第一种情况下修改引用,您将得到与第二种情况完全相同的结果。

标签: java android arraylist


【解决方案1】:

你在这两种情况下做的事情不一样。

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

myObj2.myInt = 20;

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

myStr1 = "Test_20";

如果String 是可变的,您可以通过调用某个方法来修改字符串,并且更改将反映在存储在列表中的对象中:

myStr1.setSomething(...);

另一方面,如果在第一种情况下您更改了局部变量的值,则存储在列表中的对象不会受到影响:

myObj2 = new MyClass (...);

【讨论】:

  • 对于 myStr1 = "Test_20" 的情况;如果我更改此行并添加 myString.append("_somthing");那么它也没有反映。
  • @A_user myString.append 不会变异 myString。它创建一个新的 String 实例并返回该实例。原来的String保持不变,
  • 如果我这样做会发生什么myStr1.replace("20","10") 会反映我们得到的变化吗?
  • @A_user replace 不会变异 myStr1。它创建一个新的字符串。同样,String 是不可变的,因此您在 myStr1 上调用的任何方法都不会改变它。
  • 谢谢你的解释,所以问题是String是不可变的,我们无法修改它的原始内容。
【解决方案2】:

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

当您执行String myStr2 = mTmpArray2.get(0); 时,即使您指向 ArrayList 中的引用,任何更改值的尝试都会(因为 String 不变性)创建一个不再引用 ArrayList 的新字符串 (myStr2) .

当您执行myStr1 = "xxx" 时,实际上并没有更改 ArrayList 引用,而是更改了从 ArrayList 中获取的新(副本)(现在称为 myStr1),它具有本地范围。

阅读更多关于字符串的信息:Immutability of Strings in Java

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

无关:此代码:MyClass myObj1 = new MyClass(10);(可以说)被认为是错误的。最好使用更易于阅读的 factory 模式。换句话说,带有参数的公共构造函数很难阅读(例如,当我阅读您的代码时,我不知道自己在构造什么)。

一个(也许)更好的方法是:MyClass myObj = MyClass.createInstanceWithCapacity(10); // i've invented the name because I don't know what your 10 is, but look at both, which one do you think is easier to understand upon first glance?

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

【讨论】:

  • 第一部分错了!在String myStr2 = mTmpArray2.get(0); 中没有副本,您引用了列表中的String 对象。 @Eran 的解释是对的。
  • 这里,我为你的幸福修正了措辞。
  • 这不是为了我的幸福,也不是措辞问题。查看代码,当您执行List#get(int) 时,您会明白没有 任何对象的副本。即使是不可变的String
  • @AbbéRésina 感谢您的澄清,我完全理解 List 接口(及其许多实现)是如何工作的。澄清后,在这种特殊情况下,差异是无关紧要的,因为 OP 显然不了解不变性,更不用说指针引用了;似乎是这样。
  • 同意 Abbé Résina,@MartínMarconcini 的“无关”部分,我认为使用工厂模式处理简单的事情会使代码更难理解,以防“new MyClass(10) " 10 是传递给 MyClass 的构造函数的值,它将将此值分配给它的成员 "myInt" 并创建新实例,然后我们将其添加到列表中,我给出的代码也只是为了解释问题陈述。
【解决方案3】:

字符串有一个很好的属性,叫做“不变性”

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

例如,

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 的情况下不是这样。跨度>
  • s2 = s 的情况下,s2 引用s 但是当您更改s2 时,由于字符串的不变性,更改将反映在新的结果字符串对象中。 s不变
  • 但您的输出显示s 将输出“旧字符串变为新字符串”。所以我们改变了s的值,这不应该发生。
  • 我已经纠正了我的错误。感谢您告知我错误
  • 明白了,感谢您提供的信息。
【解决方案4】:

这两个“get”调用没有区别。区别在于 ArrayList 所持有的类型与您正在执行的操作“get”方法返回的引用。

在你的第一个例子中,你这样做:

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

在这里,您在位置 0 的 ArrayList 中获得了对 MyClass 实例的引用,并且您正在修改该实例中的一个字段。

在你的第二个例子中,你这样做:

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

在这里,您将获得对数组列表中 String 实例的引用,然后您将向 myStr1 提供对您创建的不同字符串(“Test_20”)的引用。就好像你在第一个例子的第二行写了myObj2 = new MyClass(20);

因此,简而言之,在第一个示例中,您通过更改您抓取的引用来访问对象中的字段。在第二个示例中,您只是将引用更改为指向不同的字符串。

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

【讨论】:

    【解决方案5】:

    String 是一个不可变的类。像

    这样的一行
    myStr1 = "Test_20";
    

    不会改变myStr1 指向的String 对象的值。相反,会创建一个新字符串并修改myStr1 以指向新字符串。原String不变,可以从ArrayList中取回。

    您的MyClass 对象显然是可变的。只创建了一个实例,并且它的状态被赋值改变了

    myObj2.myInt = 20;
    

    因此,当从 ArrayList 中检索此对象时,会看到它的新状态。

    【讨论】:

    • 虽然正确,但实际上 String 是不可变的这一事实在这里并不重要。如果他用其他对象完成它并编写 mySomeOtherObject1 = new MySomeOtherObject();,整个事情的工作方式将完全相同
    • 同意,但其他字符串修改也会发生同样的情况,例如myStr1.replace("20","10")
    • 同意。正如 Eran 已经提到的,如果 String 不是不可变的,那么类似 myStr1.setValue("...") 的东西会起作用。
    【解决方案6】:

    您只需不要更改第二个示例中的列表。

    在第一个示例中,您正在这样做:

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

    但你的第二个例子完全不同:

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

    让我翻译一下:

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

    因此,如果您正在修改存储在列表中的对象的变量。情况二,您正在检索存储在列表中的对象 - 然后重新使用存储该检索对象的变量并将其用于新的东西 - 当然,这不会改变原始列表。

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

    【讨论】:

      猜你喜欢
      • 2013-10-22
      • 2015-05-15
      • 2012-01-20
      • 1970-01-01
      • 2014-09-08
      • 1970-01-01
      • 1970-01-01
      • 2020-06-10
      • 2015-09-21
      相关资源
      最近更新 更多