不可变的类不是那么一成不变的

时间:2015-09-06 21:38:11

标签: java

我读了this,虽然其中一个答案是有道理的,但我仍觉得有些不对劲,所以我会继续做一个例子。

假设你有这门课程:

public class ClassA {
  private List<E> mList;

  //Constructor assigns some value to mList...

  public List<E> getList() {
    return mList;
  }
}

这个类被认为是 immutable ,然而,当我在另一个类中执行类似这样的操作时,比如说ClassB,就会发生这样的事情:

public class ClassB {

  //variables and constructors....

  public void aFunction(ClassA classAObject) {
    List<E> anotherList = classAObject.getList();

    //play around with the variable anotherList, and though ClassA is immutable, I can change the value of its mList variable.
  }

}

现在我知道我可以在上面发布的链接中提到那个&#39; final&#39 ;,但是说真的,我错过了关于这个不可变概念的东西吗?我的意思是,有什么理由说它真的不是不可变的吗?

4 个答案:

答案 0 :(得分:4)

&#34; final&#34;属性仅影响对列表的引用,而不影响列表本身。如果需要不可变列表, List对象本身必须是不可变的。值得庆幸的是,有一些标准的解决方案:

答案 1 :(得分:1)

您无法更改mList的值,即为该属性指定不同的值。但您可以更改mList的内容 如果您想避免这种情况,请返回Collections.unmodifiableList(this.mList)

检查&#34;有效的Java&#34;作者:Joshua Bloch(特别是#34;最小化可变性&#34;)。

答案 2 :(得分:1)

首先,ClassA不是不可变的,也不包含List。除非在对象内作为其功能提供,否则不能通过“最终”定义来引用参考类型的不可变性。在给定的示例中,List不能是不可变的,因为它不是设计的。你可以使用:

  public List<E> getList() {
    return Collections.unmodifiableList(list);
  }

  public E[] getList() {
    return (E[]) list.toArray();
  }

用于制作列表对象 - 实际上,不是真正不可变的,以防止来自外部的更改。

作为一般建议,在封装和OOP原则方面,返回集合类型作为参考并不是一个好的做法。通过这种方式你:

  1. 打开对象内部的外部世界,
  2. 启用可能的意外更改(或必须考虑并阻止它们)
  3. 经过一段时间后(意味着从更多的地方调用代码) 更多的地方)你让你的班级很难改变和采用 未来的要求。
  4. 这就是为什么你必须从外部世界抽象出对象的内部部分并仅打开所需的部分。一般来说,引用类型的不变性更多的是它们的抽象和封装,而不是外在的。

答案 3 :(得分:0)

当所有成员字段都是不可变的,标量字段和引用,但不是必需的引用的对象时,该类被认为是不可变的。列表是这样引用的,当然是非平凡的,对象。

要创建不可变的引用对象,比如你引用的列表,需要更多的努力。

如果要扩展到引用的列表,首先将final关键字放到mList声明中,以使引用安全地不可变。好的,现在让我们保护列表。你可以:

  • 根本不要公开mList,也许只有ClassA.get(index)委托给mList.get()才足以满足您的需求。然后,您必须确保在构造函数中填充列表,并且没有对mList的其他修改访问权。
  • 创建该列表的防御性副本,并且可以安全地公开
  • 使用例如:Collections.unmodifiableList(list)
  • 在列表周围创建不可变包装器适配器

但是不要忘记列表再次只是一个引用集合,你再次只保护引用,它不保证列表项中引用对象的任何内容。

要使整个对象图不可变,如果它是您可能想要的,那将需要更多的努力。就开发工作和CPU而言,不可变性并不一定便宜,就像制作防御性副本一样。