java不可变对象问题

时间:2010-03-29 01:59:57

标签: java

String abc[]={"abc"};
String def[]={};

def=abc;
def[0]=def[0]+"changed";
System.out.println(abc[0]);

通过更改“def”对象,我的abc对象也会被更改。 String []数组旁边有哪些其他java对象具有相似特性的特性?可以解释一下吗?为了防止在我改变def时改变abc,我将不得不做def = abc.clone();

5 个答案:

答案 0 :(得分:13)

您将对象可变性/不可变性与复制参考值混淆。

在这些图表中,[var/index]是一个引用变量,而{{an Object}}是一个对象。

String abc[]={"abc"};
String def[]={};

   [abc] ------> {{a String[1]}}
                 [0] --------------> {{a String "abc"}}

   [def] ------> {{a String[0]}}

现在,您将def引用变量指向与abc引用变量相同的对象:

def=abc;

   [abc] ------> {{a String[1]}}
              /  [0] --------------> {{a String "abc"}}
             /
   [def] ---/    {{a String[0]}}

此时,长度为零的数组未被引用,并且应该是可垃圾收集的。我们可以将讨论范围缩小到长度为一的数组。请注意,String[]是引用数组。在下一行中,您更改了一个数组指向的长度中唯一的元素。

def[0]=def[0]+"changed";

   [abc] ------> {{a String[1]}}
              /  [0] ---------\      {{a String "abc"}}
             /                 \
   [def] ---/                   \--> {{a String "abcchanged"}}

请注意,{{a String "abc"}}本身并未发生变异。 [abc][def]现在指向相同的{{a String[1]}},这是可变的(即您可以使数组的元素(对String个对象的引用)指向任何东西)。


  

为了防止在我更改abc时更改def,我必须def = abc.clone();

实际上,这不太准确。让我们看看如果clone()引用了一个可变类型StringBuilder的数组,会发生什么。

    StringBuilder[] abc = new StringBuilder[] { new StringBuilder("Hello") };
    StringBuilder[] def = abc.clone();
    def[0].append(" world!");
    System.out.println(abc[0]); // prints "Hello world!"

这次我不会为你制作图表,但你可以轻松地在纸上画出来。这里发生的是,即使clone()使用自己的元素(即{{a StringBuilder[1]}})生成第二个def != abc对象,该元素也指向同一个{{a StringBuilder}}对象(即{ {1}})。


简而言之:

  • 不变性意味着某种类型的物体不能以任何有意义的方式改变为外部观察者
    • def[0] == abc[0]Integer等是不可变的
    • 通常所有的值类型都应该是
  • 数组对象是可变的
    • 它可能是对不可变类型的引用数组,但数组本身是可变的
      • 这意味着您可以将这些引用设置为您想要的任何内容
      • 同样适用于基元数组
    • 不可变数组不实用
  • 可以共享对象的引用
    • 如果对象是可变的,那么将通过所有这些引用看到变异

如果您想更深入地了解这些问题,我建议如下:

答案 1 :(得分:5)

不可变对象是一旦创建就无法更改的对象。 String是一个明显的例子。数组是可变的。如果您想要一个不可变的集合,请使用List代替:

List<String> abc = Collections.unmodifiableList(
  Arrays.asList("abc")
);

可变对象有 mutators 。 mutator是修改对象状态的任何方法。塞特斯是一个明显的例子。典型的不可变对象将如下所示:

public class Person { 
  private final String firstName;
  private final String lastName;
  private final Date dateOfBirth;

  public Person(String firstName, String lastName, Date dateOfBirth) {
    this.firstName = firstName;
    this.lastName = lastName;
    this.dateOfBirth = new Date(dateOfBirth.getTime());
  }

  public String getFirstName() { return firstName; }
  public String getLastname() { return lastName; }
  public Date getDateOfBirth() { return new Date(dateOfBirth.getTime()); }
}

一般来说,对于不可变对象,所有成员都是final且不可变的。 Date是上述问题的一个很好的例子。 Date不是一成不变的,许多人(包括我自己)认为这是设计错误。由于它是可变的,你必须进行大量的防御性复制。

答案 2 :(得分:5)

只是为了迂腐,没有“abc”对象或“def”对象。有单个String [] abc,然后def碰巧引用。这就是为什么“两个对象”都改变了。事实上,他们指的是同一个对象。

答案 3 :(得分:1)

简单来说就像这样: - 让我们假设Sample是一个类,

Sample sam1 = new Sample();

将清楚地解释为sam1是对创建的对象的引用。 但是

Sample sam2;

只是将sam2声明为Sample类型的引用变量,并且没有指向Sample类的对象。 现在如果我们做这个操作

sam2 = sam1;

然后它意味着两个引用变量都指向同一个对象,现在可以使用两个引用中的任何一个来引用该对象。 显然,可以使用任一参考文献来使用有效方法来操纵字段。 这也是这里所做的。

  String abc[]={"abc"};
  String def[]={};

  def=abc;
  def[0]=def[0]+"changed";

因此改变def [0]也会改变abc [0]。

Now when you clone you are creating a clone of the existent object. 
The clone and the cloned objects independently exist
as 2 different objects and so the result of manipulations on one 
is not reflected as you stated.

答案 4 :(得分:1)

在java中,无论数组的类型如何,您都可以随时更改数组中的元素。如果要将数据保存在数组结构中,请考虑单独创建数据副本以保护abc的初始值:

String abc[]={"abc"};
String def[];
def = Arrays.copyOf(abc, abc.length);

或者,使用cletus解决方案:

List abc = Collections.unmodifiableList(
  Arrays.asList("abc")
);