Java不可变对象问题

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


字符串是不可变的吗?看看这个链接:http://directwebremoting.org/blog/joe/2005/05/26/1117108773674.html - Humphrey Bogart
@Beau,那篇文章指出,如果你没有使用SecurityManager,你可以将不可变类更改为可变类。 - Stephen Denne
@Stephen 我的意思只是出于好奇才发帖的。 ;) - Humphrey Bogart
5个回答

15

你混淆了对象的可变性/不可变性和引用值的复制。

在这些图表中,[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 对象的引用。

为了防止更改 def 时更改 abc,我将不得不执行 def = abc.clone()

实际上,这并不完全准确。让我们看看如果你对一个可变类型 StringBuilder 的引用数组进行 clone() 会发生什么。
    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}}对象(即def [0] == abc [0])。


简而言之:

  • 不可变性意味着某种类型的对象不能以任何有意义的方式对外部观察者进行更改
    • IntegerString等是不可变的
    • 通常所有值类型都应该是不可变的
  • 数组对象是可变的
    • 它可能是一个指向不可变类型的引用数组,但该数组本身是可变的
      • 也就是说,你可以将这些引用设置为任何你想要的内容
      • 原始类型的数组也是如此
    • 不可变数组将不实用
  • 对象的引用可以共享
    • 如果对象是可变的,则通过所有这些引用将看到突变

如果您想更深入地了解这些问题,我推荐以下内容:


5

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

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

可变对象有改变器。改变器指的是任何修改对象状态的方法。Setter 是一个明显的例子。一个典型的不可变对象如下:

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不是不可变的,这被许多人(包括我自己)认为是一个设计错误。由于它是可变的,你必须进行大量的防御性复制。


1
请注意,虽然不可变的列表本身无法更改,但是单个成员并不变得不可变,并且可以被修改(除非它们已经是不可变的,例如在此示例中的字符串)。例如,一个由日期对象组成的不可变列表实际上并不是完全不可变的。 - Thilo
在进行编程时,复制“Date”是一种防御性的做法,同时也要解释为什么一开始就需要这样做是愚蠢的。 :-) - C. K. Young

5

仅仅是一点小题外话,没有“abc”对象或“def”对象。有一个String[]数组,abc和def恰好引用它。这就是为什么“两个对象”都改变了。事实上,它们指向的是同一个对象。


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.

1
在Java中,无论数组的类型是什么,您都可以随时更改数组中的元素。如果要保留数组结构中abc的初始值并希望保留数据,则考虑制作单独的数据副本:
String abc[]={"abc"};
String def[];
def = Arrays.copyOf(abc, abc.length);

或者,使用 cletus 的解决方案:

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

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