使引用不可变?

时间:2013-06-04 14:59:34

标签: java reference mutable

class Some{
  private int id;
  private String name;
  //getters and setters
}
class Check{
  private Some[] someVals;
  //getters and setters
}

假设我已将值填充到Check类

中的someVals中
void newMethod(){
      Check checkPrev = getCheckPopulated();
      Some[] someFirst = checkPrev.getSomeVals();
      modifySome(someFirst);
      Some[] some  = ? // at this point need the values of someFirst
   }

我的问题是即使在修改之后(我指定的地方)也得到Some []数组的值,即分配时首先出现的值。

很好,我会清楚地提出问题。 final Some [] someFirst = checkPrev.getSomeVals();不工作 通过保留数组值而不必将所有值重新分配给另一个数组,是否有类似于final的小提示?

5 个答案:

答案 0 :(得分:3)

你也不能吃蛋糕。您必须制作该对象的深层副本,然后修改原始副本。然后,深层副本将包含原始值。

答案 1 :(得分:1)

在modifySome方法中,返回一个新的Some []数组。

Some[] modifySome(Some[] passedArray){
  Some[] result = new Some[passedArray.length];
  System.arraycopy( passedArray, 0, result , 0, a.length );
  //Modify result as needed
  return result
}

如果你可以修改一些返回一个数组,你的代码可以改为:

Some[] some = modifySome(someFirst);

在该行之后,someFirst仍将与之前相同,some将是修改后的值。

答案 2 :(得分:0)

一种选择是使用CopyOnWriteArrayList

CopyOnWriteArrayList<Some> someFirst = checkPrev.getSomeVals();
Iterator iterator = someFirst.iterator();
modifySome(some);

迭代器仍将引用原始列表,而不是修改后的列表。

另一种选择是制作原始数组的副本。

Some[] someFirst = checkPrev.getSomeVals();
Some[] someCopy = new Some[someFirst.length];
System.arrayCopy(someFirst, 0, someCopy, 0, someFirst.length);
modifySome(some);

someCopy仍将保留原始数组的副本。

答案 3 :(得分:0)

欢迎来到可变的Java bean世界。

你无法做你想做的事......但这是一个使用我写的几个接口的解决方案:

// Both classes in the same package

@Immutable // by contract
class Some implements Frozen<SomeBuilder>
{
    // All fields are final, package local
    final String name;

    // getters only -- NO setters

    public Some(final SomeBuilder builder)
    {
        name = builder.name;
        // other
    }

    // Return a thawed version
    @Override
    public SomeBuilder thaw()
    {
        return new SomeBuilder(this);
    }
}

@NotThreadSafe // by contract
class SomeBuilder implements Thawed<Some>
{
    // Mutable fields here, package local
    String name;
    // other

    // To create a new builder
    public SomeBuilder()
    {
    }

    // Package local constructor
    SomeBuilder(final Some some)
    {
        name = some.name;
        // etc
    }

    // Mutations
    public SomeBuilder setName(final String name)
    {
        this.name = name;
        return this;
    }

    // Return a frozen version
    @Override
    public Some freeze()
    {
        return new Some(this);
    }
}

现在,关于修改功能,让它返回一个新数组。并使用.freeze() / .thaw()从现有实例创建Some的新实例。

答案 4 :(得分:0)

Java的一个缺点是基本上只有一种非原始类型:混杂的堆对象引用。类George的实例在其包之外保存对类Foo的对象Bar的引用无法与外部代码共享该引用,而不会将该外部代码赋予永久性能够对Foo George可以做的任何事情做任何事情。 Java的部分设计目标即使在简单的硬件系统上也易于实现,并且具有单个非基本类型有助于实现该目标。另一方面,它还意味着程序员需要跟踪哪些对象引用用于封装:

  • 除了身份之外的对象状态的不可变方面,即使持有引用的代码也无法更改。

  • 对象标识(以及可能是其他状态的不可变方面)

  • 对象状态的方面是可变的,除了预期它们永远不会被赋予实际改变它们的代码,而不是身份。

  • 对象状态的可变方面,这些方面由拥有引用但不具有标识的代码“拥有”。

  • 对象状态的可变方面,以及身份。

在你的代码中,因为数组是可变的,你的数组类型字段不能具有第一个含义,但它可以保存其他四个中的任何一个。此外,数组的元素可以包含任何上述类型的东西。如果您认为对象的状态是其数组中保存的idname对的组合,那么id和/或name的{​​{1} Some持有引用的对象可以更改,如果这样的更改将被视为Check 状态的更改,则制作副本Check的状态需要创建一个新数组,并用新的Check实例填充它,其数据是从原始数组中的相应实例复制的。

如果数组中的Some个对象都不会暴露给可能会改变它们的代码,那么就没有必要构造单个Check个对象的新实例;创建一个新数组并使用对原始数组中对象的引用来填充它就足够了。同样,如果数组的目的是封装在其他位置定义的Check个对象的标识,那么对这些对象的更改将不会被视为对Check的更改州。请注意,在前一种情况下(对象永远不会更改),用保存相同数据的新实例替换Check对象将效率低下但不会破坏任何内容。在后一种情况下(数组封装了对象的标识,而不是它们的状态),用新实例的引用替换引用会破坏代码。

虽然许多人谈论“深度克隆”或“浅层克隆”,但这种术语主要源于对各种对象引用应该封装的内容缺乏明确性。如果对象Some具有类型类型字段,该字段封装Fred拥有的可变状态(但不包含身份),则Fred的副本应该包含对该副本的引用宾语。如果字段封装了不可变状态,则Fred的副本可以保存对原始对象或其任何不可变副本的引用。如果它封装了身份,则Fred 的副本必须保存对原始对象的引用 - 而不是副本。如果它封装了身份和可变状态,则无法复制Fred而不复制它所属的相互连接对象的整个林。