定义不可变对象的策略

时间:2016-04-21 12:25:03

标签: java object immutability

我是Java的新手,正在尝试通过Java tutorial Oracle学习定义不可变对象的策略。由于我的知识有限,可能是措辞如何,我真的很难理解下面的段落,我只是想知道是否有人可以向我解释它的实际含义。在此先感谢您的帮助!

  

不要共享对可变对象的引用。永远不要存储参考   传递给构造函数的外部可变对象;如有必要,   创建副本,并存储对副本的引用。同样,创造   必要时避免使用内部可变对象的副本   在你的方法中返回原件。

4 个答案:

答案 0 :(得分:2)

不要分享对可变对象的引用

public class A {
  private List<Integer> privateRef;
  private int privateVal = 0;

  public List<Integer> bad() {
      return this.privateRef;
  }

  public int good() {
      return privateVal;
  }

}

bad()方法很糟糕,因为它暴露了对私有成员的引用,从而使调用者能够执行此操作:

A a = new A();
List<Integer> extractedRef = a.bad();
extractedRef.add(1);

因此改变了一个私有列表(如果你检查a.privateList,你会看到它包含1)。 A不再控制其内部状态。

good()方法很好,因为即使调用者这样做:

A a = new A();
int number = a.good();
number++;

即使number为1,a.privateVal的值仍为0。

永远不要存储对传递给构造函数的外部可变对象的引用

想象一下,我们将这个构造函数添加到我们的类中:

public class A {
    ...
    public A(List<Integer> anotherList) {
        this.privateRef = anotherList;
    } 
    ...
}

我们处于类似情况:私人名单的突变可能发生在外部:

List<Integer> list = new ArrayList<Integer>()
A a = new A(list);
list.add(1);

我们已经改变了一个内部状态(如果你检查a.privateList,你会看到它包含1)。

如有必要,创建副本并存储对副本的引用。

也就是说,如果你希望A是不可变的,你应该这样做:

public A(List<Integer> anotherList) {
    this.privateRef = new ArrayList<>(anotherList);
} 

这样,A的新实例在创建时获得列表的副本,这将成为其内部状态的一部分。从这一点来看,给定列表的突变(&#34;列表&#34;在示例中)不会影响内部状态:只有a可以改变其私有列表。

同样,必要时创建内部可变对象的副本,以避免在方法中返回原始文件。

这就是你解决第一个例子的方法,如果A想要暴露其内部可变列表,它不应该这样做:

  public List<Integer> bad() {
      return this.privateRef;
  }

  public List<Integer> better() {
      return new ArrayList<>(this.privateRef);
  }

此时

A a = new A();
List<Integer> extractedRef = a.better();
extractedRef.add(1)

此时,a.privateRef仍为空(因此保护其状态免受外部突变)。但extractRef将包含1。

另外一点。请注意,即使此类适用上述所有原则:

public class A {
    private List<Integer> privateRef = new ArrayList<>();
    private int privateVal = 0;

    public A(List) {
        this.privateRef.addAll(this.privateRef);
    }

    public List<Integer> getList() {
        return new ArrayList<>(this.privateRef);
    }

    public int getVal() {
        return privateVal;
    }

    public void mutate() {
        this.privateVal++;
        this.privateRef.clear();
    }

}

(它没有公开对可变对象的引用,也没有保留对外部可变对象的引用),它并不是真正不可变的,因为有一种方法可以改变它的内部状态(对它调用mutate())。 / p>

你当然可以删除mutate(),但更正确的不变性替代方法可能是:

public class A {
    private final List<Integer> privateRef = new ArrayList<>();
    private final int privateVal = 0;

    public A(List) {
        this.privateRef.addAll(this.privateRef);
    }

    public List<Integer> getList() {
        return new ArrayList<>(this.privateRef);
    }

    public int getVal() {
        return privateVal;
    }

}

(我还没有编译这些例子,但它们应该几乎没问题。)

答案 1 :(得分:0)

实际上它说你不想把你的国家暴露给外面的世界,所以外面的世界可以根据自己的意愿修改你的内部状态。您不希望这样,因为这是您在该内部状态下的业务,有人可能会改变它。这也是为什么它会重新创建你内部可变对象的副本并将它们作为副本传递出去的原因。他们会改变这些副本,但不会对你的内部状态产生副作用。

答案 2 :(得分:0)

示例:

/**
 * A simple mutable.
 */
class Mutable {

    private int n;

    public Mutable(int n) {
        this.n = n;
    }

    public int getN() {
        return n;
    }

    public void setN(int n) {
        this.n = n;
    }

    @Override
    public String toString() {
        return "Mutable{" + "n=" + n + '}';
    }

}

// A thing that holds things.
class Thing<T> {

    T m;

    public Thing(T m) {
        this.m = m;
    }

    public T getM() {
        return m;
    }

    @Override
    public String toString() {
        return "Thing{" + "m=" + m + '}';
    }

}

public void test() {
    // Never store references to external, mutable objects passed to the constructor
    Mutable m = new Mutable(10);
    // t10 holds a reference to my mutable `m`, currently containing `10`
    Thing<Mutable> t10 = new Thing<>(m);
    // Now `m` holds `50`, even the one in `t10`.
    m.setN(50);
    // Make the new one holding `m` at value `50` now.
    Thing<Mutable> t50 = new Thing<>(m);
    // Both print the same because they both hold the same `m`
    System.out.println("t10 = " + t10 + " t50 = " + t50);
    // We can even mess with it after the fact - this is why you should return a copy.
    t50.getM().setN(42);
    // Both print the same because they both hold the same `m`
    System.out.println("t10 = " + t10 + " t50 = " + t50);
}

这证明了所有三点。

  • 如果您将mutable传递给构造函数并在该状态中保持该变量,则对该变量的更改将更改该对象的状态。

  • 如果您返回mutable,也可以在对象外修改,从而改变您的状态。

为避免这种情况:

  • 如果可能,请在所有情况下使用immutable
  • 如果您必须在构造函数中使用mutable,请尽可能复制它。
  • 如果您在任何地方返回mutable,请尽可能返回副本。

答案 3 :(得分:0)

在创建Immutable对象时,如果有像Date对象这样的可变字段,则应用以下行:

  

不要共享对可变对象的引用。永远不要存储对传递给构造函数的外部可变对象的引用;如有必要,创建副本并存储对副本的引用。同样,必要时创建内部可变对象的副本,以避免在方法中返回原始文件。

同样可以实现如下:

  • 创建不可变对象时创建Date对象的防御副本。
  • 不要为此Date Object提供任何setter方法。
  • 如果你需要提供getter方法,那么getter方法应该返回date字段的副本,因为Date本身是可变的。在这里,clone()方法有助于实现这一目标。

现在,由于类中没有对Date字段的引用,因此该类之外的任何外部都不能修改Immutable类的状态。

检查Example for Immutable class