是否有一个Javareflection实用程序来深入比较两个对象?

我正在尝试为大型项目中的各种clone()操作编写unit testing,我想知道是否有一个现有的类可以接受两个相同types的对象,并进行深入比较,说他们是否相同?

Unitils有这个function:

通过reflection来实现等式断言,具有不同的选项,如忽略Java默认/空值和忽略集合的顺序

我喜欢这个问题! 主要是因为它几乎没有回答或回答不好。 这就像没有人知道这一点。 处女领地:)

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

“没有办法扩展一个可实例化的类,并在保持等价合同的同时添加一个方面”

除了布尔方法还有什么好处呢? 实际上封装原始和克隆之间的所有差异会很好,你不觉得吗? 另外,我假设你不希望为图中的每个对象编写/维护比较代码,而是在寻找随着时间的推移而随着源变化的东西。

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

您将遇到的一些问题:

  • 收集顺序:如果两个collections夹保持相同的对象,但是按不同的顺序,它们应该被认为是相似的吗?
  • 哪些领域忽略:瞬态? 静态的?
  • types等价:字段值应该是完全相同的types吗? 或者可以扩展另一个呢?
  • 还有更多,但我忘了…

XStream非常快速,并且与XMLUnit结合只需几行代码即可完成这项工作。 XMLUnit是很好的,因为它可以报告所有的差异,或者只停留在find的第一个差异。 它的输出包括xpath到不同的节点,这很好。 默认情况下,它不允许无序的集合,但可以configuration它。 注入一个特殊的差异处理程序(称为DifferenceListener )允许您指定处理差异的方式,包括忽略顺序。 但是,只要你想做最简单的定制之外的任何事情,就很难编写,细节往往被束缚在一个特定的领域对象上。

我个人的偏好是使用reflection循环遍历所有声明的领域,并深入到每个领域,跟踪差异。 警告词:除非你喜欢堆栈溢出exception,否则不要使用recursion。 用堆栈保留范围(使用LinkedList或其他)。 我通常忽略瞬态和静态字段,而且我跳过了已经比较过的对象对,所以如果有人决定编写自引用代码,我不会在无限循环中结束(不过,无论如何,我总是比较原始包装器,因为相同的对象参考经常被重用)。 你可以事先configuration,忽略集合sorting,忽略特殊types或字段,但我喜欢通过注释在字段上定义自己的状态比较策略。 这,恕我直言,正是什么注释的意思,使关于在运行时可用的类的元数据。 就像是:

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

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

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

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

这个类完全是原作者所要求的。

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

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

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

您可以比较两个相同types的对象,它会显示更改,添加和删除。 如果没有变化,那么对象是相等的(理论上)。 为检查过程中应忽略的获得者提供注释。 框架工作比平等检查有更广泛的应用,即我正在使用生成更改日志。

在比较JPA实体时,它的性能是可以的,一定要先将它们从实体pipe理器中分离出来。

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; } } 
 User user1 = new User(1, "John", "Doe"); User user2 = new User(1, "John", "Doe"); assertReflectionEquals(user1, user2); 

您的链接列表示例并不难处理。 当代码遍历两个对象图时,它将访问的对象放在Set或Map中。 在遍历另一个对象引用之前,testing这个集合以查看对象是否已经遍历。 如果是这样,就不用走了。

我同意上面的人谁说使用LinkedList(就像一个堆栈,但没有同步的方法,所以它更快)。 使用Stack来遍历对象图,而使用reflection来获取每个字段是理想的解决scheme。 一次编写,这个“外部”equals()和“external”hashCode()是所有equals()和hashCode()方法应该调用的。 再也不需要客户的equals()方法。

我写了一些代码遍历一个完整的对象图,在Google Code上列出。 请参阅json-io(http://code.google.com/p/json-io/)。; 它将Java对象图序列化为JSON并从中反序列化。 它处理所有的Java对象,有或没有公共构造函数,Serializeable或不可序列化等等。这个相同的遍历代码将是外部“equals()”和外部“hashcode()”实现的基础。 顺便说一下,JsonReader / JsonWriter(json-io)通常比内build的ObjectInputStream / ObjectOutputStream更快。

这个JsonReader / JsonWriter可以用来进行比较,但是这对hashcode没有帮助。 如果你想要一个通用的hashcode()和equals(),它需要它自己的代码。 我可以用一个普通的graphics访问者来解决这个问题。 我们拭目以待。

其他注意事项 – 静态字段 – 很简单 – 可以跳过它们,因为所有的equals()实例对于静态字段都具有相同的值,因为静态字段在所有实例之间共享。

至于瞬态领域 – 这将是一个可select的select。 有时你可能希望暂时不计数。 “有时你觉得自己像个坚果,有时候你不会。”

回顾一下json-io项目(我的其他项目),你会发现外部的equals()/ hashcode()项目。 我还没有一个名字,但它是显而易见的。

如果你的对象实现Serializable你可以使用这个:

 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); } } 

您可以使用EqualsBuilder.reflectionEquals()重写对象的equals()方法,如下所述:

  public boolean equals(Object obj) { return EqualsBuilder.reflectionEquals(this, obj); } 

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

这种事情是为什么.equals在Object中定义的。

如果这样做一贯你不会有问题。

Hamcrest拥有Matcher samePropertyValuesAs 。 但它依赖于JavaBeans公约(使用getters和setters)。 如果要比较的对象没有获取者和设置者的属性,这将不起作用。

 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 } } 

用户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; } } 

停止这种深度比较的保证可能是一个问题。 以下应该做什么? (如果你实现了这样一个比较器,这将是一个很好的unit testing。)

 LinkedListNode a = new LinkedListNode(); a.next = a; LinkedListNode b = new LinkedListNode(); b.next = b; System.out.println(DeepCompare(a, b)); 

这是另一个:

 LinkedListNode c = new LinkedListNode(); LinkedListNode d = new LinkedListNode(); c.next = d; d.next = c; System.out.println(DeepCompare(c, d)); 

我认为Ray Hulha解决scheme最简单的解决scheme是序列化对象,然后深入比较原始结果。

序列化可以是字节,json,xml或简单的toString等。ToString似乎更便宜。 龙目岛为我们生成免费的易于定制的ToSTring。 看下面的例子。

 @ToString @Getter @Setter class foo{ boolean foo1; String foo2; public boolean deepCompare(Object other) { //for cohesiveness return other != null && this.toString().equals(other.toString()); } } 

Apache为您提供了一些东西,将两个对象转换为string并比较string,但是您必须重写toString()

 obj1.toString().equals(obj2.toString()) 

覆盖toString()

如果所有字段都是原始types:

 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--; } } }}