如何确定一个类是否是不可变的

时间:2016-05-07 11:09:24

标签: java oop immutability

在给我的问题具体化之前,让我提供一些背景知识:我的主要编程语言是C ++和Java。使用C ++时,我发现应用const正确性非常重要,即声明如下函数:

A::DoSomething( const B& arg ); // guarantees that arg is not modified
A::DoSomething() const; // guarantees that the object of type A is not modified
A::DoSomething( const B& arg ) const; // both of the above

事实上,我经常希望const是默认值,并且必须以某种方式标记修改后的对象。

使用const的主要原因是:

  • 与其他开发者的沟通:它使代码更具表现力。
  • 与编译器的通信:它有助于在编译时查找问题,有时可以进行其他优化。

众所周知,Java没有const关键字(您不能使用final执行上述操作)并且此事已在此处讨论过,请参阅此处:{{3 }}

通常建议的Java替代方法是使您的类不可变。虽然这不是const的完全替代,因为它适用于每个类而不是每个使用类的上下文,但在大多数情况下它对我来说都可以正常工作。

但是不可变类有一个大问题:不可变性不明显。 要知道一个类是否真的是不可变的,据我所知,你基本上必须检查完整的源代码。任何方法都可以有一个后门,通过它可以修改对象。

那么有更简单的方法来检查不变性吗?或者是否有任何最佳实践以某种方式将一个类标记为不可变?

注意:我知道这两种语言都提供了绕过常量或不变性的“邪恶技巧”:C ++有const_cast而Java有反射。但是对于我的问题,我们假设没有使用它们。

2 个答案:

答案 0 :(得分:14)

Java没有一流的不变性支持,所以你没有可靠的方法来检测类是否是不可变的。

Java Concurrency In Practice建议(参见附录A作为参考)使用@Immutable中的类级javax.annotation.concurrent注释,这是表示不变性的最方便,最常用和最标准的方法;自定义javadoc也很好。请注意,它只是声明,而不是实际约束。

课程设计也是一个很好的指标:

  • 只有最终字段(但在极少数情况下,它可能有一些非最终字段并且仍然是不可变的,例如参见String#hashCode)
  • 构造正确(this引用不会从构造函数中泄漏)
  • 无法修改对象状态(因此类不应该有setter和mutator方法)
  • 不要存储外部(传递给构造函数)对可变对象的引用(例如,创建传递的集合参数的防御副本)

可以在Oracle tutorials中找到不可变类设计属性的完整列表。

因此,要检查类是否是不可变的,首先要查看类级注释和javadoc,然后再查看实现本身。

为了提供额外的健全性检查(如果您认为注释为不可变类可能是错误的可变),FindBugs(静态分析工具)有Mutability Detector插件,它有效地执行上面列出的相同操作:检查class具有@Immutable注释并验证(通过反射)所有不变性规则都得到满足(还有一些其他的东西,比如来自Guava等的不可变集合支持)。可变性检测器也可以用作没有FindBugs的库,它允许你编写类似的测试:

@Test
public void testImmutable() {
    assertImmutable(MyClass.class);
}

答案 1 :(得分:-1)

根据Java documentation

  • 不要提供" setter" methods - 修改字段引用的字段或对象的方法。
  • 将所有字段设为最终字段并保密。
  • 不允许子类覆盖方法。最简单的方法是将类声明为final。更复杂的方法是使构造函数私有,并在工厂方法中构造实例。
  • 如果实例字段包含对可变对象的引用,则不允许更改这些对象:
    • 不要提供修改可变对象的方法。
    • 不要共享对可变对象的引用。永远不要存储对传递给构造函数的外部可变对象的引用;如有必要,创建副本并存储对副本的引用。同样,必要时创建内部可变对象的副本,以避免在方法中返回原始文件。

强制实现不变性的最简单方法是通过Lombok库中的 @Value 注释。

如果使用IntelliJ,则可以检查上述项目符号的类字节代码 enter image description here