是否有 Java 反射实用程序来对两个对象进行深入比较?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/1449001/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Is there a Java reflection utility to do a deep comparison of two objects?
提问by Uri
I'm trying to write unit tests for a variety of clone()
operations inside a large project and I'm wondering if there is an existing class somewhere that is capable of taking two objects of the same type, doing a deep comparison, and saying if they're identical or not?
我正在尝试为clone()
大型项目中的各种操作编写单元测试,我想知道某处是否有一个现有的类能够获取相同类型的两个对象,进行深入比较,然后说它们是否是否相同?
回答by Wolfgang
回答by Wolfgang
I am usin XStream:
我正在使用 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();
}
回答by Bill K
I guess you know this, but In theory, you're supposed to always override .equals to assert that two objects are truly equal. This would imply that they check the overridden .equals methods on their members.
我想你知道这一点,但理论上,你应该总是覆盖 .equals 来断言两个对象真的相等。这意味着他们会检查其成员上覆盖的 .equals 方法。
This kind of thing is why .equals is defined in Object.
这种事情就是为什么 .equals 被定义在 Object 中。
If this were done consistently you wouldn't have a problem.
如果一直这样做,你就不会有问题。
回答by Kevin C
I love this question! Mainly because it is hardly ever answered or answered badly. It's like nobody has figured it out yet. Virgin territory :)
我喜欢这个问题!主要是因为它几乎没有人回答或回答得很差。好像还没有人弄明白。处女地 :)
First off, don't even thinkabout using equals
. The contract of equals
, as defined in the javadoc, is an equivalence relation (reflexive, symmetric, and transitive), notan equality relation. For that, it would also have to be antisymmetric. The only implementation of equals
that is (or ever could be) a true equality relation is the one in java.lang.Object
. Even if you did use equals
to compare everything in the graph, the risk of breaking the contract is quite high. As Josh Bloch pointed out in Effective Java, the contract of equals is very easy to break:
首先,甚至不要考虑使用equals
. equals
在 javadoc 中定义的 的契约是等价关系(自反、对称和传递),而不是等价关系。为此,它也必须是反对称的。唯一的实现equals
是(或永远可能是)真正的平等关系是java.lang.Object
. 即使您确实使用equals
过比较图表中的所有内容,违约的风险也相当高。正如 Josh Bloch 在Effective Java 中指出的,equals 的契约很容易被打破:
"There is simply no way to extend an instantiable class and add an aspect while preserving the equals contract"
“根本没有办法扩展一个可实例化的类并添加一个方面,同时保留 equals 合同”
Besides what good does a boolean method really do you anyway? It'd be nice to actually encapsulate all the differences between the original and the clone, don't you think? Also, I'll assume here that you don't want to be bothered with writing/maintaining comparison code for each object in the graph, but rather you're looking for something that will scale with the source as it changes over time.
除了布尔方法真的对你有什么好处?实际上封装原始和克隆之间的所有差异会很好,你不觉得吗?此外,我在这里假设您不想为图表中的每个对象编写/维护比较代码而烦恼,而是您正在寻找可以随源随时间变化而扩展的东西。
Soooo, what you really want is some kind of state comparison tool. How that tool is implemented is really dependent on the nature of your domain model and your performance restrictions. In my experience, there is no generic magic bullet. And it willbe slow over a large number of iterations. But for testing the completeness of a clone operation, it'll do the job pretty well. Your two best options are serialization and reflection.
Soooo,你真正想要的是某种状态比较工具。该工具的实现方式实际上取决于域模型的性质和性能限制。根据我的经验,没有通用的灵丹妙药。并且在大量迭代后会变慢。但是为了测试克隆操作的完整性,它可以很好地完成这项工作。您的两个最佳选择是序列化和反射。
Some issues you will encounter:
你会遇到的一些问题:
- Collection order: Should two collections be considered similar if they hold the same objects, but in a different order?
- Which fields to ignore: Transient? Static?
- Type equivalence: Should field values be of exactly the same type? Or is it ok for one to extend the other?
- There's more, but I forget...
- 集合顺序:如果两个集合持有相同的对象,但顺序不同,是否应该被认为是相似的?
- 要忽略哪些字段:瞬态?静止的?
- 类型等效:字段值是否应该是完全相同的类型?或者一个可以扩展另一个吗?
- 还有更多,但我忘记了...
XStream is pretty fast and combined with XMLUnit will do the job in just a few lines of code. XMLUnit is nice because it can report all the differences, or just stop at the first one it finds. And its output includes the xpath to the differing nodes, which is nice. By default it doesn't allow unordered collections, but it can be configured to do so. Injecting a special difference handler (Called a DifferenceListener
) allows you to specify the way you want to deal with differences, including ignoring order. However, as soon as you want to do anything beyond the simplest customization, it becomes difficult to write and the details tend to be tied down to a specific domain object.
XStream 非常快,结合 XMLUnit 只需几行代码就可以完成这项工作。XMLUnit 很好,因为它可以报告所有差异,或者只是在它找到的第一个差异处停止。它的输出包括不同节点的 xpath,这很好。默认情况下,它不允许无序集合,但可以配置为这样做。注入特殊的差异处理程序(称为 a DifferenceListener
)允许您指定处理差异的方式,包括忽略顺序。然而,一旦您想要做任何超出最简单定制的事情,就会变得难以编写,并且细节往往与特定的域对象相关联。
My personal preference is to use reflection to cycle through all the declared fields and drill down into each one, tracking differences as I go. Word of warning: Don't use recursion unless you like stack overflow exceptions. Keep things in scope with a stack (use a LinkedList
or something). I usually ignore transient and static fields, and I skip object pairs that I've already compared, so I don't end up in infinite loops if someone decided to write self-referential code (However, I always compare primitive wrappers no matter what, since the same object refs are often reused). You can configure things up front to ignore collection ordering and to ignore special types or fields, but I like to define my state comparison policies on the fields themselves via annotations. This, IMHO, is exactly what annotations were meant for, to make meta data about the class available at runtime. Something like:
我个人的偏好是使用反射来循环浏览所有声明的字段并深入到每个字段,在我进行时跟踪差异。警告词:除非您喜欢堆栈溢出异常,否则不要使用递归。使用堆栈将事物保持在范围内(使用LinkedList
或者其他的东西)。我通常会忽略瞬态和静态字段,并跳过我已经比较过的对象对,因此如果有人决定编写自引用代码,我不会以无限循环结束(但是,无论如何,我总是比较原始包装器,因为相同的对象引用经常被重用)。您可以预先配置一些内容以忽略集合排序并忽略特殊类型或字段,但我喜欢通过注释在字段本身上定义我的状态比较策略。恕我直言,这正是注释的目的,使有关该类的元数据在运行时可用。就像是:
@StatePolicy(unordered=true, ignore=false, exactTypesOnly=true)
private List<StringyThing> _mylist;
I think this is actually a really hard problem, but totally solvable! And once you have something that works for you, it is really, really, handy :)
我认为这实际上是一个非常困难的问题,但完全可以解决!一旦你有了适合你的东西,它真的非常非常方便:)
So, good luck. And if you come up with something that's just pure genius, don't forget to share!
所以,祝你好运。如果你想出一些纯粹的天才,不要忘记分享!
回答by Ben Voigt
A halting guarantee for such a deep comparison might be a problem. What should the following do? (If you implement such a comparator, this would make a good unit test.)
如此深入的比较的停止保证可能是一个问题。下面应该怎么做?(如果你实现了这样的比较器,这将是一个很好的单元测试。)
LinkedListNode a = new LinkedListNode();
a.next = a;
LinkedListNode b = new LinkedListNode();
b.next = b;
System.out.println(DeepCompare(a, b));
Here's another:
这是另一个:
LinkedListNode c = new LinkedListNode();
LinkedListNode d = new LinkedListNode();
c.next = d;
d.next = c;
System.out.println(DeepCompare(c, d));
回答by John DeRegnaucourt
Your Linked List example is not that difficult to handle. As the code traverses the two object graphs, it places visited objects in a Set or Map. Before traversing into another object reference, this set is tested to see if the object has already been traversed. If so, no need to go further.
您的链接列表示例并不难处理。当代码遍历两个对象图时,它会将访问过的对象放置在 Set 或 Map 中。在遍历到另一个对象引用之前,会测试该集合以查看该对象是否已被遍历。如果是这样,就没有必要再进一步了。
I agree with the person above who said use a LinkedList (like a Stack but without synchronized methods on it, so it is faster). Traversing the object graph using a Stack, while using reflection to get each field, is the ideal solution. Written once, this "external" equals() and "external" hashCode() is what all equals() and hashCode() methods should call. Never again do you need a customer equals() method.
我同意上面说使用 LinkedList 的人(就像一个 Stack 但上面没有同步方法,所以它更快)。使用 Stack 遍历对象图,同时使用反射获取每个字段,是理想的解决方案。写一次,这个“外部”equals() 和“外部”hashCode() 是所有 equals() 和 hashCode() 方法应该调用的。您再也不需要客户 equals() 方法了。
I wrote a bit of code that traverses a complete object graph, listed over at Google Code. See json-io (http://code.google.com/p/json-io/). It serializes a Java object graph into JSON and deserialized from it. It handles all Java objects, with or without public constructors, Serializeable or not Serializable, etc. This same traversal code will be the basis for the external "equals()" and external "hashcode()" implementation. Btw, the JsonReader / JsonWriter (json-io) is usually faster than the built-in ObjectInputStream / ObjectOutputStream.
我编写了一些遍历完整对象图的代码,在 Google Code 上列出。请参阅 json-io (http://code.google.com/p/json-io/)。它将 Java 对象图序列化为 JSON 并从中反序列化。它处理所有 Java 对象,有或没有公共构造函数、可序列化或不可序列化等。同样的遍历代码将是外部“equals()”和外部“hashcode()”实现的基础。顺便说一句,JsonReader / JsonWriter (json-io) 通常比内置的 ObjectInputStream / ObjectOutputStream 快。
This JsonReader / JsonWriter could be used for comparison, but it will not help with hashcode. If you want a universal hashcode() and equals(), it needs it's own code. I may be able to pull this off with a generic graph visitor. We'll see.
这个 JsonReader / JsonWriter 可以用于比较,但它对哈希码没有帮助。如果你想要一个通用的 hashcode() 和 equals(),它需要它自己的代码。我也许可以用一个通用的图形访问者来解决这个问题。我们拭目以待。
Other considerations - static fields - that's easy - they can be skipped because all equals() instances would have the same value for static fields, as the static fields is shared across all instances.
其他注意事项 - 静态字段 - 这很容易 - 可以跳过它们,因为所有 equals() 实例对于静态字段都具有相同的值,因为静态字段在所有实例之间共享。
As for transient fields - that will be a selectable option. Sometimes you may want transients to count other times not. "Sometimes you feel like a nut, sometimes you don't."
至于瞬态字段 - 这将是一个可选选项。有时您可能希望瞬态计数其他时间不计数。“有时你觉得自己像个疯子,有时你没有。”
Check back to the json-io project (for my other projects) and you will find the external equals() / hashcode() project. I don't have a name for it yet, but it will be obvious.
返回 json-io 项目(对于我的其他项目),您会发现外部 equals() / hashcode() 项目。我还没有它的名字,但它会很明显。
回答by John DeRegnaucourt
See DeepEquals and DeepHashCode() within java-util: https://github.com/jdereg/java-util
请参阅 java-util 中的 DeepEquals 和 DeepHashCode():https: //github.com/jdereg/java-util
This class does exactly what the original author requests.
这个类完全符合原作者的要求。
回答by Ryan
Just had to implement comparison of two entity instances revised by Hibernate Envers. I started writing my own differ but then found the following framework.
只需要实现 Hibernate Envers 修改的两个实体实例的比较。我开始编写自己的不同但后来找到了以下框架。
https://github.com/SQiShER/java-object-diff
https://github.com/SQiShER/java-object-diff
You can compare two objects of the same type and it will show changes, additions and removals. If there are no changes, then the objects are equal (in theory). Annotations are provided for getters that should be ignored during the check. The frame work has far wider applications than equality checking, i.e. I am using to generate a change-log.
您可以比较两个相同类型的对象,它会显示更改、添加和删除。如果没有变化,那么对象是相等的(理论上)。为在检查期间应忽略的 getter 提供了注释。该框架的应用比平等检查要广泛得多,即我用来生成更改日志。
Its performance is OK, when comparing JPA entities, be sure to detach them from the entity manager first.
它的性能还可以,在比较JPA实体时,一定要先将它们从实体管理器中分离出来。
回答by gavenkoa
http://www.unitils.org/tutorial-reflectionassert.html
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);
回答by cheffe
Hamcrest has the Matcher samePropertyValuesAs. But it relies on the JavaBeans Convention (uses getters and setters). Should the objects that are to be compared not have getters and setters for their attributes, this will not work.
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
}
}
The user bean - with getters and setters
用户 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;
}
}