如何确定类是否不可变

2022-09-04 20:38:42

在具体化我的问题之前,让我提供一些背景知识:我的主要编程语言是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没有关键字(你不能用上面做),这个事实之前已经在这里讨论过了,例如在这里:Java中const(C++)的等效物constfinal

通常提出的Java替代方案是使您的类不可变。虽然这不是完全替代,因为它适用于每个类,而不是每个使用类的上下文,但在大多数情况下,它对我来说工作得很好。const

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

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

注意:我知道这两种语言都提供了绕过恒定性或不可变性的“邪恶技巧”:C++具有const_cast,Java具有反射。但是对于我的问题的上下文,让我们假设这些没有被使用。


答案 1

Java 没有一流的不可变性支持,因此您没有可靠的方法来检测类是否不可变。

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

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

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

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

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

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

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

答案 2

根据Java文档

  • 不要提供“setter”方法,即修改字段或字段引用的对象的方法。
  • 使所有字段成为最终字段并设为私有字段。
  • 不允许子类重写方法。执行此操作的最简单方法是将类声明为 final。更复杂的方法是使构造函数私有并在工厂方法中构造实例。
  • 如果实例字段包含对可变对象的引用,则不允许更改这些对象:
    • 不要提供修改可变对象的方法。
    • 不要共享对可变对象的引用。切勿存储对传递给构造函数的外部可变对象的引用;如有必要,请创建副本,并存储对副本的引用。同样,必要时创建内部可变对象的副本,以避免在方法中返回原始文件。

强制不可变性的最简单方法是在龙目岛库中@Value注释。

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