是否有Java反射实用程序来对两个对象进行深度比较?

Uri*_*Uri 91 java comparison equals

我正在尝试为clone()大型项目中的各种操作编写单元测试,我想知道是否有一个现有的类能够获取两个相同类型的对象,进行深入比较,并说是否'相同与否?

Wol*_*ang 62

Unitils具有此功能:

通过反射进行平等断言,使用不同的选项,例如忽略Java默认/空值并忽略集合的顺序

  • 我已经对这个函数进行了一些测试,它似乎进行了深入的比较,而EqualsBuilder却没有. (9认同)

小智 29

我喜欢这个问题!主要是因为它几乎没有得到很好的回答或回答.这就像没有人想出来的那样.处女地区:)

首先,甚至不要考虑使用equals.equals在javadoc中定义的契约是等价关系(自反,对称和传递),而不是等式关系.为此,它也必须是反对称的.唯一的实现equals是(或永远可能)真正的平等关系就是这个java.lang.Object.即使您确实用于equals比较图表中的所有内容,违约的风险也很高.正如Josh Bloch在Effective Java中指出的那样,平等契约很容易打破:

"在保留equals合同的同时,根本无法扩展可实例化的类并添加方面"

除了布尔方法有什么好处之外你呢?真的很好地封装原始和克隆之间的所有差异,你不觉得吗?此外,我在这里假设您不希望为图中的每个对象编写/维护比较代码而烦恼,而是在寻找随着时间的推移而随源变化的内容.

Soooo,你真正想要的是某种状态比较工具.该工具的实现方式实际上取决于域模型的性质和性能限制.根据我的经验,没有通用的魔术子弹.而且在大量迭代中它很慢.但是为了测试克隆操作的完整性,它将很好地完成工作.您最好的两个选项是序列化和反射.

您将遇到的一些问题:

  • 收集顺序:如果两个集合拥有相同的对象,但它们的顺序不同,是否应该被认为是相似的?
  • 哪些字段要忽略:瞬态?静态的?
  • 类型等价:字段值应该是完全相同的类型吗?或者是否可以扩展另一个?
  • 还有更多,但我忘了......

XStream非常快,与XMLUnit相结合只需几行代码即可完成.XMLUnit很不错,因为它可以报告所有差异,或者只是停在它找到的第一个差异上.它的输出包括不同节点的xpath,这很好.默认情况下,它不允许无序集合,但可以配置为执行此操作.注入特殊差异处理程序(称为a DifferenceListener)允许您指定处理差异的方式,包括忽略顺序.但是,只要您想要执行除最简单的自定义之外的任何操作,就会很难编写,并且细节往往会绑定到特定的域对象.

我个人的偏好是使用反射循环遍历所有声明的字段并向下钻取每个字段,跟踪差异.警告:除非您喜欢堆栈溢出异常,否则不要使用递归.使用堆栈将事物放在范围内(使用aLinkedList或者其他的东西).我通常忽略瞬态和静态字段,并且我跳过我已经比较的对象对,所以如果有人决定编写自引用代码,我不会在无限循环中结束(但是,我总是比较原始包装器,不管是什么,因为相同的对象引用经常被重用).您可以预先配置以忽略集合排序并忽略特殊类型或字段,但我喜欢通过注释在字段本身上定义状态比较策略.恕我直言,这正是注释的意思,以便在运行时提供有关该类的元数据.就像是:


@StatePolicy(unordered=true, ignore=false, exactTypesOnly=true)
private List<StringyThing> _mylist;
Run Code Online (Sandbox Code Playgroud)

我认为这实际上是一个非常难的问题,但完全可以解决!一旦你有适合自己的东西,它真的非常方便:)

祝你好运.如果你想出的东西只是纯粹的天才,别忘了分享!


Joh*_*urt 14

请参阅java-util中的DeepEquals和DeepHashCode():https://github.com/jdereg/java-util

这个类完全符合原作者的要求.

  • 警告:DeepEquals使用对象的.equals()方法(如果存在).这可能不是你想要的. (4认同)
  • 如果明确添加了equals()方法,它只对类使用.equals(),否则它会进行逐个成员比较.这里的逻辑是,如果有人去努力编写自定义的equals()方法,那么应该使用它.未来的增强:允许flag让它忽略equals()方法,即使它们存在.java-util中有一些有用的实用程序,如CaseInsensitiveMap/Set. (4认同)

Lou*_*adi 8

覆盖equals()方法

你可以简单地覆盖equals()方法使用类的方法EqualsBuilder.reflectionEquals()作为解释在这里:

 public boolean equals(Object obj) {
   return EqualsBuilder.reflectionEquals(this, obj);
 }
Run Code Online (Sandbox Code Playgroud)


小智 7

只需要实现Hibernate Envers修改的两个实体实例的比较.我开始写自己的不同,但后来发现了以下框架.

https://github.com/SQiShER/java-object-diff

您可以比较两个相同类型的对象,它将显示更改,添加和删除.如果没有变化,则对象相等(理论上).为检查期间应忽略的getter提供注释.框架工作具有比等式检查更广泛的应用程序,即我用于生成更改日志.

它的性能还可以,比较JPA实体时,请务必先将它们从实体管理器中分离出来.


小智 6

我在XStream中使用:

/**
 * @see java.lang.Object#equals(java.lang.Object)
 */
@Override
public boolean equals(Object o) {
    XStream xstream = new XStream();
    String oxml = xstream.toXML(o);
    String myxml = xstream.toXML(this);

    return myxml.equals(oxml);
}

/**
 * @see java.lang.Object#hashCode()
 */
@Override
public int hashCode() {
    XStream xstream = new XStream();
    String myxml = xstream.toXML(this);
    return myxml.hashCode();
}
Run Code Online (Sandbox Code Playgroud)

  • 列表以外的集合可能以不同的顺序返回元素,因此字符串比较将失败. (5认同)

gav*_*koa 5

http://www.unitils.org/tutorial-reflectionassert.html

public class User {

    private long id;
    private String first;
    private String last;

    public User(long id, String first, String last) {
        this.id = id;
        this.first = first;
        this.last = last;
    }
}
Run Code Online (Sandbox Code Playgroud)
User user1 = new User(1, "John", "Doe");
User user2 = new User(1, "John", "Doe");
assertReflectionEquals(user1, user2);
Run Code Online (Sandbox Code Playgroud)

  • 如果您必须处理生成的类,而这对平等没有任何影响,则特别有用! (2认同)

che*_*ffe 5

Hamcrest 有 Matcher samePropertyValuesAs。但它依赖于 JavaBeans 约定(使用 getter 和 setter)。如果要比较的对象的属性没有 getter 和 setter,则这将不起作用。

import static org.hamcrest.beans.SamePropertyValuesAs.samePropertyValuesAs;
import static org.junit.Assert.assertThat;

import org.junit.Test;

public class UserTest {

    @Test
    public void asfd() {
        User user1 = new User(1, "John", "Doe");
        User user2 = new User(1, "John", "Doe");
        assertThat(user1, samePropertyValuesAs(user2)); // all good

        user2 = new User(1, "John", "Do");
        assertThat(user1, samePropertyValuesAs(user2)); // will fail
    }
}
Run Code Online (Sandbox Code Playgroud)

用户 bean - 具有 getter 和 setter

public class User {

    private long id;
    private String first;
    private String last;

    public User(long id, String first, String last) {
        this.id = id;
        this.first = first;
        this.last = last;
    }

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getFirst() {
        return first;
    }

    public void setFirst(String first) {
        this.first = first;
    }

    public String getLast() {
        return last;
    }

    public void setLast(String last) {
        this.last = last;
    }

}
Run Code Online (Sandbox Code Playgroud)


Vla*_*scu 5

AssertJ中,您可以执行以下操作:

Assertions.assertThat(expectedObject).isEqualToComparingFieldByFieldRecursively(actualObject);
Run Code Online (Sandbox Code Playgroud)

可能并非在所有情况下都可以使用,但是在您认为更多的情况下都可以使用。

文档说明如下:

根据属性/字段比较(包括继承的对象)的递归属性/字段,确定被测对象(实际)等于给定对象。如果实际的equals实现不适合您,这将很有用。递归属性/字段比较不适用于具有自定义equals实现的字段,即,将使用覆盖的equals方法代替逐字段比较的字段。

递归比较处理周期。默认情况下,浮点数的精度为1.0E-6,而浮点数的精度为1.0E-15。

您可以为每个(嵌套的)字段或类型指定自定义比较器,分别使用ComparatorForFields(Comparator,String ...)和usingComparatorForType(Comparator,Class)。

要比较的对象可以是不同的类型,但必须具有相同的属性/字段。例如,如果实际对象具有一个名称String字段,则期望另一个对象也具有一个。如果对象具有字段和具有相同名称的属性,则将在该字段上使用属性值。

  • `isEqualToComparingFieldByFieldRecursively` 现已弃用。使用 `assertThat(expectedObject).usingRecursiveComparison().isEqualTo(actualObject);` 代替:) (3认同)