如何确定 class 是否不可变
How to find out if a class is immutable
在具体化我的问题之前,让我提供一些背景知识:我的主要编程语言是 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
做上面的事情)并且这个事实已经在这里讨论过,请参阅此处示例:Equivalent of const(C++) in Java.
通常提出的 Java 替代方案是使您的 classes 不可变。虽然这不是 const
的完全替代,因为它适用于 class 而不是每个使用 class 的上下文,但在大多数情况下它对我来说效果很好。
但是不可变 classes 有一个大问题:不可变性并不明显。 class 确实是不可变的,据我所知,你基本上必须检查完整的源代码。任何方法都可能有一个后门,通过它可以修改对象。
那么有没有更简单的方法来检查不变性呢?或者是否有任何最佳实践以某种方式将 class 标记为不可变?
注意:我知道这两种语言都提供 'evil tricks' 来绕过常量性或不变性:C++ 有 const_cast
和 Java 有反射。但是对于我的问题的上下文,我们假设没有使用这些。
Java 没有 first-class 不变性支持,因此您没有可靠的方法来检测 class 是否不可变。
Java Concurrency In Practice推荐(参见附录A)使用javax.annotation.concurrent
的class级@Immutable
注解,这是最方便、通用和标准的方式表明不变性;自定义 javadoc 也很好。请注意,它只是声明,而不是实际约束。
设计的class也是一个很好的指标:
- 只有 final 字段(但在极少数情况下,可能有一些非 final 字段并且仍然是不可变的,例如参见 String#hashCode)
- 构造正确(
this
引用未从构造函数中泄漏)
- 无法修改对象状态(因此 class 不应该有 setter 和 mutator 方法)
- 不要存储外部(传递给构造函数)对可变对象的引用(例如创建传递的集合参数的防御副本)
不可变 class 设计属性的完整列表可以在 Oracle tutorials.
中找到
因此,要检查 class 是否不可变,您首先要查看 class 级别的注释和 javadoc,然后再查看实现本身。
为了提供额外的健全性检查(如果你认为注释为不可变的 class 可能会错误地可变),FindBugs(静态分析工具)有 Mutability Detector 插件,它有效地做上面列出的是同样的事情:检查 class 有 @Immutable
注释并验证(通过反射)所有不变性规则都得到满足(还有一些额外的东西,比如来自 Guava 的不可变集合支持等)。可变检测器也可以用作没有 FindBugs 的库,它允许您编写这样的测试:
@Test
public void testImmutable() {
assertImmutable(MyClass.class);
}
- 不提供 "setter" 方法 — 修改字段或字段引用的对象的方法。
- 将所有字段设为最终字段和私有字段。
- 不允许子class重写方法。最简单的方法是将 class 声明为 final。更复杂的方法是将构造函数设为私有并在工厂方法中构造实例。
- 如果实例字段包含对可变对象的引用,则不允许更改这些对象:
- 不要提供修改可变对象的方法。
- 不要共享对可变对象的引用。永远不要存储对传递给构造函数的外部可变对象的引用;如有必要,创建副本并存储对副本的引用。同样,必要时创建内部可变对象的副本,以避免在方法中返回原始对象。
实现不变性的最简单方法是通过 Lombok 库中的 @Value 注释。
如果你使用 IntelliJ,你可以检查 class 字节码以获取上述项目符号
在具体化我的问题之前,让我提供一些背景知识:我的主要编程语言是 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
做上面的事情)并且这个事实已经在这里讨论过,请参阅此处示例:Equivalent of const(C++) in Java.
通常提出的 Java 替代方案是使您的 classes 不可变。虽然这不是 const
的完全替代,因为它适用于 class 而不是每个使用 class 的上下文,但在大多数情况下它对我来说效果很好。
但是不可变 classes 有一个大问题:不可变性并不明显。 class 确实是不可变的,据我所知,你基本上必须检查完整的源代码。任何方法都可能有一个后门,通过它可以修改对象。
那么有没有更简单的方法来检查不变性呢?或者是否有任何最佳实践以某种方式将 class 标记为不可变?
注意:我知道这两种语言都提供 'evil tricks' 来绕过常量性或不变性:C++ 有 const_cast
和 Java 有反射。但是对于我的问题的上下文,我们假设没有使用这些。
Java 没有 first-class 不变性支持,因此您没有可靠的方法来检测 class 是否不可变。
Java Concurrency In Practice推荐(参见附录A)使用javax.annotation.concurrent
的class级@Immutable
注解,这是最方便、通用和标准的方式表明不变性;自定义 javadoc 也很好。请注意,它只是声明,而不是实际约束。
设计的class也是一个很好的指标:
- 只有 final 字段(但在极少数情况下,可能有一些非 final 字段并且仍然是不可变的,例如参见 String#hashCode)
- 构造正确(
this
引用未从构造函数中泄漏) - 无法修改对象状态(因此 class 不应该有 setter 和 mutator 方法)
- 不要存储外部(传递给构造函数)对可变对象的引用(例如创建传递的集合参数的防御副本)
不可变 class 设计属性的完整列表可以在 Oracle tutorials.
中找到因此,要检查 class 是否不可变,您首先要查看 class 级别的注释和 javadoc,然后再查看实现本身。
为了提供额外的健全性检查(如果你认为注释为不可变的 class 可能会错误地可变),FindBugs(静态分析工具)有 Mutability Detector 插件,它有效地做上面列出的是同样的事情:检查 class 有 @Immutable
注释并验证(通过反射)所有不变性规则都得到满足(还有一些额外的东西,比如来自 Guava 的不可变集合支持等)。可变检测器也可以用作没有 FindBugs 的库,它允许您编写这样的测试:
@Test
public void testImmutable() {
assertImmutable(MyClass.class);
}
- 不提供 "setter" 方法 — 修改字段或字段引用的对象的方法。
- 将所有字段设为最终字段和私有字段。
- 不允许子class重写方法。最简单的方法是将 class 声明为 final。更复杂的方法是将构造函数设为私有并在工厂方法中构造实例。
- 如果实例字段包含对可变对象的引用,则不允许更改这些对象:
- 不要提供修改可变对象的方法。
- 不要共享对可变对象的引用。永远不要存储对传递给构造函数的外部可变对象的引用;如有必要,创建副本并存储对副本的引用。同样,必要时创建内部可变对象的副本,以避免在方法中返回原始对象。
实现不变性的最简单方法是通过 Lombok 库中的 @Value 注释。
如果你使用 IntelliJ,你可以检查 class 字节码以获取上述项目符号