是否有Java实用程序对两个对象进行深入比较?

是否有Java实用程序对两个对象进行深入比较?,java,comparison,equals,Java,Comparison,Equals,如何“深入”-根据测试中的字段值比较两个未实现equals方法的对象 原始问题(因缺乏精确性而关闭,因此不符合SO标准),保留用于记录目的: 我正在尝试为大型项目中的各种clone()操作编写单元测试,我想知道是否有一个现有的类能够获取相同类型的两个对象,进行深入比较,并判断它们是否相同?具有以下功能: 通过反射进行相等断言,使用不同的选项,如忽略Java默认值/空值和忽略集合顺序 我正在使用XStream: /** * @see java.lang.Object#equals(java.l

如何“深入”-根据测试中的字段值比较两个未实现equals方法的对象


原始问题(因缺乏精确性而关闭,因此不符合SO标准),保留用于记录目的:

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

具有以下功能:

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

我正在使用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();
}

我想你知道这一点,但在理论上,你应该总是覆盖。等于来断言两个对象是真正相等的。这意味着它们检查其成员上重写的.equals方法

这就是为什么。equals是在Object中定义的


如果一直这样做,你就不会有问题。

我喜欢这个问题!主要是因为它很少被回答或者回答得不好。好像还没人弄明白。处女地:)

首先,不要考虑使用
equals
。正如javadoc中定义的那样,
等于
的契约是一种等价关系(自反、对称和传递),而不是一种等价关系。因此,它也必须是反对称的。
equals
的唯一实现是(或可能是)真正的相等关系,它是
java.lang.Object
中的一个。即使您确实使用了
equals
来比较图表中的所有内容,违反合同的风险也相当高。正如Josh Bloch在《有效Java》中指出的那样,平等的契约很容易被打破:

“在保留equals契约的同时,根本无法扩展可实例化类并添加方面” 除此之外,布尔方法到底对你有什么好处?把原始版本和克隆版本之间的所有差异都封装起来会很好,你不这么认为吗?此外,我将在这里假设您不想为图形中的每个对象编写/维护比较代码,而是希望在源代码随时间变化时,能够根据源代码进行缩放

Soooo,你真正想要的是某种状态比较工具。该工具的实现方式实际上取决于域模型的性质和性能限制。根据我的经验,没有通用的灵丹妙药。而且在大量的迭代过程中速度会很慢。但是为了测试克隆操作的完整性,它会做得很好。您的两个最佳选择是序列化和反射

您将遇到的一些问题:

  • 集合顺序:如果两个集合包含相同的对象,但顺序不同,是否应该认为它们相似
  • 哪些字段要忽略:瞬态?静电
  • 类型等价:字段值是否应该是完全相同的类型?或者一个可以扩展另一个
  • 还有很多,但我忘了
XStream非常快,与XMLUnit结合使用只需几行代码即可完成这项工作。XMLUnit很好,因为它可以报告所有的差异,或者只在发现第一个差异时停止。它的输出包括到不同节点的xpath,这很好。默认情况下,它不允许无序集合,但可以将其配置为这样做。注入一个特殊的差异处理程序(称为
DifferenceListener
)允许您指定处理差异的方式,包括忽略顺序。但是,只要您想做最简单的自定义之外的任何事情,就很难编写,而且细节往往会绑定到特定的域对象

我个人的偏好是使用反射在所有声明的字段中循环,并深入到每个字段中,跟踪差异。警告:除非您喜欢堆栈溢出异常,否则不要使用递归。使用堆栈将内容保持在范围内(使用
LinkedList
或其他东西)。我通常忽略瞬态和静态字段,并且跳过已经比较过的对象对,因此如果有人决定编写自引用代码,我不会以无限循环结束(然而,我总是比较基本包装器,因为相同的对象引用经常被重用)。您可以预先配置忽略集合顺序和忽略特殊类型或字段,但我喜欢通过注释在字段本身上定义状态比较策略。IMHO正是注释的目的,它使类的元数据在运行时可用。比如:


@StatePolicy(unordered=true, ignore=false, exactTypesOnly=true)
private List<StringyThing> _mylist;

@StatePolicy(无序=真,忽略=假,exactTypesOnly=真)
私人名单;
我认为这实际上是一个非常困难的问题,但完全可以解决!一旦你有了适合你的东西,它就真的、真的、方便了:)


所以,祝你好运。如果你想出了一些纯粹的天才,别忘了分享

对于如此深入的比较,一个停顿的保证可能是一个问题。下面应该怎么做?(如果实现这样一个比较器,这将是一个很好的单元测试。)

还有一个:

LinkedListNode c = new LinkedListNode();
LinkedListNode d = new LinkedListNode();
c.next = d;
d.next = c;

System.out.println(DeepCompare(c, d));

您的链表示例并不难处理。当代码遍历两个对象图时,它将访问的对象放置在一个集合或映射中。在遍历到另一个对象引用之前,将测试此集合,以查看该对象是否已被遍历。如果是这样,就没有必要再进一步了

我同意上面提到的使用LinkedList(类似于堆栈,但没有同步方法,因此速度更快)的人的观点。使用堆栈遍历对象图,同时使用反射获取每个场
LinkedListNode c = new LinkedListNode();
LinkedListNode d = new LinkedListNode();
c.next = d;
d.next = c;

System.out.println(DeepCompare(c, d));
public static boolean deepCompare(Object o1, Object o2) {
    try {
        ByteArrayOutputStream baos1 = new ByteArrayOutputStream();
        ObjectOutputStream oos1 = new ObjectOutputStream(baos1);
        oos1.writeObject(o1);
        oos1.close();

        ByteArrayOutputStream baos2 = new ByteArrayOutputStream();
        ObjectOutputStream oos2 = new ObjectOutputStream(baos2);
        oos2.writeObject(o2);
        oos2.close();

        return Arrays.equals(baos1.toByteArray(), baos2.toByteArray());
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}
@ToString @Getter @Setter
class foo{
    boolean foo1;
    String  foo2;        
    public boolean deepCompare(Object other) { //for cohesiveness
        return other != null && this.toString().equals(other.toString());
    }
}   
 public boolean equals(Object obj) {
   return EqualsBuilder.reflectionEquals(this, obj);
 }
obj1.toString().equals(obj2.toString())
import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
@Override
public String toString() {return 
ReflectionToStringBuilder.toString(this);}
// Within class
import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
@Override
public String toString() {return 
ReflectionToStringBuilder.toString(this,new 
MultipleRecursiveToStringStyle());}

// New class extended from Apache ToStringStyle
import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import java.util.*;

public class MultipleRecursiveToStringStyle extends ToStringStyle {
private static final int    INFINITE_DEPTH  = -1;

private int                 maxDepth;

private int                 depth;

public MultipleRecursiveToStringStyle() {
    this(INFINITE_DEPTH);
}

public MultipleRecursiveToStringStyle(int maxDepth) {
    setUseShortClassName(true);
    setUseIdentityHashCode(false);

    this.maxDepth = maxDepth;
}

@Override
protected void appendDetail(StringBuffer buffer, String fieldName, Object value) {
    if (value.getClass().getName().startsWith("java.lang.")
            || (maxDepth != INFINITE_DEPTH && depth >= maxDepth)) {
        buffer.append(value);
    } else {
        depth++;
        buffer.append(ReflectionToStringBuilder.toString(value, this));
        depth--;
    }
}

@Override
protected void appendDetail(StringBuffer buffer, String fieldName, 
Collection<?> coll) {
    for(Object value: coll){
        if (value.getClass().getName().startsWith("java.lang.")
                || (maxDepth != INFINITE_DEPTH && depth >= maxDepth)) {
            buffer.append(value);
        } else {
            depth++;
            buffer.append(ReflectionToStringBuilder.toString(value, this));
            depth--;
        }
    }
}

@Override
protected void appendDetail(StringBuffer buffer, String fieldName, Map<?, ?> map) {
    for(Map.Entry<?,?> kvEntry: map.entrySet()){
        Object value = kvEntry.getKey();
        if (value.getClass().getName().startsWith("java.lang.")
                || (maxDepth != INFINITE_DEPTH && depth >= maxDepth)) {
            buffer.append(value);
        } else {
            depth++;
            buffer.append(ReflectionToStringBuilder.toString(value, this));
            depth--;
        }
        value = kvEntry.getValue();
        if (value.getClass().getName().startsWith("java.lang.")
                || (maxDepth != INFINITE_DEPTH && depth >= maxDepth)) {
            buffer.append(value);
        } else {
            depth++;
            buffer.append(ReflectionToStringBuilder.toString(value, this));
            depth--;
        }
    }
}}
Assertions.assertThat(expectedObject).isEqualToComparingFieldByFieldRecursively(actualObject);