是的,您可以编写一个不变性检测器。
首先,您不会只是编写一个确定类是否不可变的方法。相反,您将需要编写一个不变性检测器类,因为它必须保持某种状态。检测器的状态将是它迄今为止检查过的所有类的检测到的不变性。这不仅对性能有用,而且实际上是必要的,因为一个类可能包含循环引用,这会导致简单的不变性检测器陷入无限递归。
类的不变性有四个可能的值:Unknown、Mutable、Immutable 和 Calculating。您可能希望有一个映射,它将您迄今为止遇到的每个类与一个不变性值相关联。当然,Unknown 实际上并不需要实现,因为它将是尚未在映射中的任何类的隐含状态。
因此,当您开始检查一个类时,将其与映射中的 Calculating 值相关联,完成后,将 Calculating 替换为 Immutable 或 Mutable。
对于每个类,您只需要检查字段成员,而不需要检查代码。检查字节码的想法是相当错误的。
首先,你应该不检查一个类是否是final的;类的最终确定性不会影响其不变性。相反,一个期望一个不可变参数的方法应该首先调用不可变检测器来断言传递的实际对象的类的不变性。如果参数的类型是 final 类,这个测试可以省略,所以 finality 对性能有好处,但严格来说没有必要。此外,正如您将在下面看到的那样,类型为非最终类的字段将导致声明类被认为是可变的,但这仍然是声明类的问题,而不是非最终类的问题不可变成员类。拥有不可变类的高层次结构是非常好的,其中所有非叶节点当然必须是非最终的。
你应该不检查一个字段是否是私有的;一个类拥有一个公共字段是完全可以的,并且该字段的可见性不会以任何方式、形状或形式影响声明类的不变性。您只需要检查该字段是否为final,其类型是否不可变。
在检查一个类时,您首先要做的是递归确定其super 类的不变性。如果 super 是可变的,那么根据定义,后代也是可变的。
那么,您只需要检查类的已声明字段,而不是所有字段。
如果一个字段不是最终的,那么你的类是可变的。
如果一个字段是最终的,但该字段的类型是可变的,那么你的类是可变的。 (数组根据定义是可变的。)
如果一个字段是final,并且该字段的类型是Calculating,则忽略它并继续下一个字段。如果所有字段都是不可变的或Calculating,那么您的类是不可变的。
如果字段的类型是接口、抽象类或非最终类,那么它被认为是可变的,因为您完全无法控制实际实现可能会做什么。这似乎是一个无法克服的问题,因为这意味着将可修改的集合包装在 UnmodifiableCollection 中仍然无法通过不变性测试,但实际上没问题,可以通过以下解决方法来处理。
某些类可能包含非最终字段,但仍然实际上是不可变的。 String 类就是一个例子。其他属于这一类的类是包含非最终成员纯粹用于性能监控目的的类(调用计数器等),实现 popsicle immutability 的类(查找),以及包含成员是已知不会引起任何副作用的接口。此外,如果一个类包含真正的可变字段,但承诺在计算 hashCode() 和 equals() 时不考虑它们,那么该类在多线程方面当然是不安全的,但它仍然可以被视为为了将其用作地图中的键,它是不可变的。因此,所有这些情况都可以通过以下两种方式之一来处理:
手动将类(和接口)添加到不变性检测器。如果您知道某个类实际上是不可变的,尽管它的不变性测试失败了,您可以手动向检测器添加一个条目,将其与Immutable 关联。这样,检测器将永远不会尝试检查它是否是不可变的,它只会说“是的,它是”。
引入@ImmutabilityOverride 注释。您的不变性检测器可以检查字段上是否存在此注释,如果存在,它可能会将字段视为不可变,尽管该字段可能是非最终的或其类型可能是可变的。检测器还可以检查类上是否存在此注释,从而将类视为不可变的,甚至无需检查其字段。
我希望这对后代有所帮助。