代码之家  ›  专栏  ›  技术社区  ›  Frank Puffer

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

  •  13
  • Frank Puffer  · 技术社区  · 10 年前

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

    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 将是默认值,并且必须以某种方式标记被修改的对象。

    我使用的主要原因 常量 是:

    • 与其他开发人员交流:它使代码更具表现力。
    • 与编译器的通信:它有助于在编译时发现问题,有时还可以进行额外的优化。

    众所周知,Java没有 常量 关键字(不能使用 final )这一事实之前已经在这里讨论过了,请参见此处的示例: Equivalent of const(C++) in Java .

    通常建议的Java替代方案是使类不可变。虽然这不是完全替代 常量 因为它适用于每个类,而不是每个使用类的上下文,所以在大多数情况下对我来说都很好。

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

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

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

    2 回复  |  直到 9 年前
        1
  •  18
  •   qwwdfsad    10 年前

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

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

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

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

    不可变类设计属性的完整列表可以在 Oracle tutorials .

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

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

    @Test
    public void testImmutable() {
        assertImmutable(MyClass.class);
    }
    
        2
  •  -2
  •   max3d    8 年前

    根据 Java documentation :

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

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

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