java 对象不变性的单元测试

myss37ts  于 2023-05-12  发布在  Java
关注(0)|答案(6)|浏览(146)

我想确保一组给定的对象是不可变的。
我在想一件事
1.检查是否每个字段都是private final
1.检查类是否为final
1.检查可变成员
我想我的问题是:3.可能吗?
我可以递归地检查类的每个成员是否都有其字段private final,但这还不够,因为类可以有一个名为getHaha(param)的方法,该方法将给定的参数添加到数组中。
那么,有没有一种好的方法来检查一个对象是否是不可变的,或者它是否是可能的?
谢啦

w8f9ii69

w8f9ii691#

你可能想看看这个项目:
Mutability Detector
这个库试图分析特定类的字节码,以发现它是否是不可变的。它允许在单元测试中对这种情况进行测试,如here视频所示。它当然不是完美的(String字段将被认为是可变的,并且您的数组示例处理得不好),但它比FindBugs提供的更复杂(即仅检查每个字段是否为最终字段)。
免责声明:是我写的;- )

x33g5p2x

x33g5p2x2#

如果您生成了数据模型及其所有代码,则可以确保您创建的可能的数据值对象是不可变的,以满足您的需求。
你遇到的问题是不变性有不同的形式。即使是String也无法通过测试Are String, Date, Method immutable?您可以通过这种方式证明类是严格不可变的,但您可能更好地生成数据模型。

tktrz96b

tktrz96b3#

是的,你可以写一个不变性检测器。
首先,你不会仅仅写一个方法来决定一个类是否是不可变的;相反,您将需要编写一个不变性检测器类,因为它必须维护一些状态。检测器的状态将是检测到的到目前为止它已经检查过的所有类的不变性。这不仅对性能有用,而且实际上是必要的,因为类可能包含循环引用,这将导致简单的不变性检测器陷入无限递归。
类的不变性有四种可能的值:UnknownMutableImmutableCalculating。您可能希望有一个Map,它将您目前遇到的每个类关联到一个不变性值。当然,Unknown实际上并不需要实现,因为它将是尚未在Map中的任何类的隐含状态。
因此,当您开始检查一个类时,将它与Map中的Calculating值相关联,完成后,将Calculating替换为ImmutableMutable
对于每个类,您只需要检查字段成员,而不是代码。检查字节码的想法是相当误导的。
首先,你应该而不是检查一个类是否是final的;一个类的终结性并不影响它的不变性。相反,一个期望一个不可变参数的方法应该首先调用不可变检测器来Assert所传递的实际对象的类的不可变性。如果参数的类型是final类,则可以省略此测试,因此finality对性能有好处,但严格地说不是必需的。此外,正如你将在下面看到的,一个类型为非final类的字段将导致声明类被认为是可变的,但这仍然是声明类的问题,而不是非final不可变成员类的问题。拥有一个高层次的不可变类是非常好的,其中所有的非叶节点当然都必须是非final的。

    • 不**检查字段是否为私有;对于一个类来说,有一个公共字段是完全可以的,并且字段的可见性不会以任何方式、形状或形式影响声明类的不可变性。你只需要检查字段是否是final的,其类型是否是不可变的。

当检查一个类时,首先要做的是递归确定它的super类的不变性。如果super是可变的,那么后代也是可变的。
然后,您只需要检查类的 * 声明 * 字段,而不是 * 所有 * 字段。
如果一个字段是非final的,那么你的类是可变的。
如果一个字段是final的,但是字段的类型是可变的,那么你的类是可变的。(数组根据定义是可变的。)
如果一个字段是final,并且字段的类型是Calculating,则忽略它并继续下一个字段。如果所有字段都是不可变的或Calculating,则类是不可变的。
如果字段的类型是一个接口,或者一个抽象类,或者一个非final类,那么它被认为是可变的,因为你绝对不能控制实际的实现可能会做什么。这似乎是一个无法克服的问题,因为这意味着在UnmodifiableCollection中 Package 一个可修改的集合仍然无法通过不变性测试,但它实际上是好的,可以通过以下解决方法来处理。
有些类可能包含非final字段,但仍然是 * 有效不可变 * 的。例如String类。其他属于这一类别的类是包含纯粹用于性能监控目的的非final成员的类(调用计数器等),实现 * 冰棒不变性 *(查找)的类,以及包含已知不会引起任何副作用的接口成员的类。此外,如果一个类包含真正的可变字段,但承诺在计算hashCode()和equals()时不考虑它们,那么当涉及到多线程时,这个类当然是不安全的,但它仍然可以被认为是不可变的,以便将其用作map中的键。因此,所有这些情况都可以通过以下两种方式之一进行处理:
1.手动添加类(和接口)到您的不变性检测器。如果您知道某个类实际上是不可变的,尽管它的不可变性测试失败了,您可以手动向检测器添加一个条目,将其与Immutable相关联。这样,检测器将永远不会试图检查它是否是不可变的,它总是说“是的,它是”。

1.引入@ImmutabilityOverride注解。不变性检测器可以检查字段上是否存在此注解,如果存在,它可能会将字段视为不可变的,尽管字段可能是非final的或其类型可能是可变的。检测器还可以检查类上的该注解的存在,从而将类视为不可变的,甚至不需要费心检查其字段。
我希望这对后代有所帮助。

2023年编辑

我最终编写了一个实现上述所有内容的库。
你可以在这里找到它:https://github.com/mikenakis/Bathyscaphe
在我看来,像MutabilityDetector这样的静态分析工具(参见Grundlefleck的回答)在某些时候和某些场景下可能是有用的,但它们并没有(也不能)在任何时候都为问题提供完整的解决方案。一个完整的解决方案需要运行时检查,正如我在我的博客上解释的那样:https://blog.michael.gr/2022/05/bathyscaphe.html,这就是我写Bathyscaphe的原因。

vsaztqbk

vsaztqbk4#

我怀疑你能用单元测试做到这一点。最好的方法是在编写类或查看代码时要小心。正是因为对象上的方法可以改变它的状态,而你可能从外部看不到这个问题。仅仅因为它是气馁并不意味着它不会发生:-)

pod7payv

pod7payv5#

我敢肯定这是不可能的。考虑这个函数:

public void doSomething() {
    if (System.currentTimeMillis() % 100000 == 0) {
        this.innerMember.changeState();
    }
}

首先,你不能通过运行每个类函数来检测它,因为这个函数在100秒内只精确地改变对象的状态一次。
第二,您无法通过解析代码来检测它,因为您不知道changeState()函数是否更改了innerMember的状态。

ipakzgxi

ipakzgxi6#

这个线程可以帮助How do I identify immutable objects in Java。看看第二个流行的答案,也许可以用FindBugs检查任何不变性问题。如果你在每次提交时都运行它,那么你可以称之为单元测试:)

  • 编辑 *

FindBugs似乎只检查final,这并不多。您可以根据代码中使用的模式和类实现自己的规则。

相关问题