是否有Java实用程序对两个对象进行深入比较?
如何“深入”-根据测试中的字段值比较两个未实现equals方法的对象是否有Java实用程序对两个对象进行深入比较?,java,comparison,equals,Java,Comparison,Equals,如何“深入”-根据测试中的字段值比较两个未实现equals方法的对象 原始问题(因缺乏精确性而关闭,因此不符合SO标准),保留用于记录目的: 我正在尝试为大型项目中的各种clone()操作编写单元测试,我想知道是否有一个现有的类能够获取相同类型的两个对象,进行深入比较,并判断它们是否相同?具有以下功能: 通过反射进行相等断言,使用不同的选项,如忽略Java默认值/空值和忽略集合顺序 我正在使用XStream: /** * @see java.lang.Object#equals(java.l
原始问题(因缺乏精确性而关闭,因此不符合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,你真正想要的是某种状态比较工具。该工具的实现方式实际上取决于域模型的性质和性能限制。根据我的经验,没有通用的灵丹妙药。而且在大量的迭代过程中速度会很慢。但是为了测试克隆操作的完整性,它会做得很好。您的两个最佳选择是序列化和反射
您将遇到的一些问题:
- 集合顺序:如果两个集合包含相同的对象,但顺序不同,是否应该认为它们相似
- 哪些字段要忽略:瞬态?静电
- 类型等价:字段值是否应该是完全相同的类型?或者一个可以扩展另一个
- 还有很多,但我忘了
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);