Я пытаюсь написать модульные тесты для различных операций clone()
внутри большого проекта, и мне интересно, есть ли где-то существующий класс, способный принимать два объекта одного типа, делая глубокие сравнение, и если они идентичны или нет?
Есть ли утилита отражения Java для глубокого сравнения двух объектов?
Ответ 1
Unitils имеет следующие функции:
Утверждение равенства через отражение, с различными вариантами, такими как игнорирование значений по умолчанию /null Java и игнорирование порядка коллекций
Ответ 2
Мне нравится этот вопрос! Главным образом потому, что он вряд ли когда-либо ответил или ответил плохо. Как будто никто еще не понял этого. Территория Богородицы:)
Во-первых, даже не думайте об использовании equals
. Контракт equals
, как определено в javadoc, является отношением эквивалентности (рефлексивным, симметричным и транзитивным), а не отношением равенства. Для этого он также должен быть антисимметричным. Единственной реализацией equals
, которая (или когда-либо может быть), является истинное равенство равенства в java.lang.Object
. Даже если вы использовали equals
для сравнения всего на графике, риск взлома контракта довольно высок. Как отметил Джош Блох в "Эффективной Java", контракт на равных очень легко сломать:
"Просто невозможно расширить экземпляр класса и добавить аспект при сохранении равного контракта"
Кроме того, что хорошо делает логический метод на самом деле? Было бы неплохо на самом деле инкапсулировать все различия между оригиналом и клоном, не так ли? Кроме того, я предполагаю, что вы не хотите беспокоиться о написании/поддержании кода сравнения для каждого объекта на графике, но скорее вы ищете что-то, что будет масштабироваться с источником, поскольку оно изменяется со временем.
Soooo, то, что вы действительно хотите, это какой-то инструмент сравнения состояний. Как этот инструмент реализован, действительно зависит от характера вашей модели домена и ограничений производительности. По моему опыту, нет никакой универсальной магической пули. И он будет медленным при большом количестве итераций. Но для проверки полноты операции клонирования это будет хорошо работать. Ваши два лучших варианта - сериализация и отражение.
Некоторые проблемы, с которыми вы столкнетесь:
- Порядок коллекции: следует ли рассматривать две коллекции, если они содержат одни и те же объекты, но в другом порядке?
- Какие поля игнорировать: переходный? Статическая?
- Типовая эквивалентность: должны ли значения полей быть одного типа? Или это нормально для того, чтобы расширить другой?
- Там больше, но я забыл...
XStream довольно быстро и в сочетании с XMLUnit выполнит эту работу всего за несколько строк кода. XMLUnit хорош, потому что он может сообщать обо всех различиях или просто останавливаться при первом обнаружении. И его вывод включает в себя xpath для разных узлов, что приятно. По умолчанию он не разрешает неупорядоченные коллекции, но его можно настроить таким образом. Внедрение специального обработчика различий (Called a DifferenceListener
) позволяет указать способ, которым вы хотите иметь дело с различиями, включая игнорирование порядка. Однако, как только вы захотите сделать что-либо, кроме простой настройки, становится трудно писать, и детали, как правило, привязаны к определенному объекту домена.
Мое личное предпочтение - использовать рефлексию для циклического перехода по всем объявленным полям и развернуть их в каждом, отслеживая различия по мере того, как я иду. Слово предупреждения: не используйте рекурсию, если вам не нравятся исключения. Держите вещи в области с помощью стека (используйте LinkedList
или что-то еще). Обычно я игнорирую переходные и статические поля, и я пропускаю пары объектов, которые я уже сравнивал, поэтому я не заканчиваю бесконечные циклы, если кто-то решил написать самореференционный код (однако, я всегда сравниваю примитивные обертки, независимо от того, что, так как тот же объект refs часто используется повторно). Вы можете настроить вещи, чтобы игнорировать упорядочение коллекции и игнорировать специальные типы или поля, но мне нравится определять мои политики сравнения состояний в самих полях с помощью аннотаций. Это, ИМХО, является именно тем, для чего предназначались аннотации, чтобы метаданные о классе были доступны во время выполнения. Что-то вроде:
@StatePolicy(unordered=true, ignore=false, exactTypesOnly=true)
private List<StringyThing> _mylist;
Я думаю, что это действительно очень трудная проблема, но полностью разрешимая! И как только у вас есть что-то, что работает для вас, это действительно, действительно, удобно:)
Итак, удачи. И если вы придумаете что-то, что просто чистым гением, не забудьте поделиться!
Ответ 3
См. раздел DeepEquals и DeepHashCode() в java-util: https://github.com/jdereg/java-util
Этот класс выполняет именно то, что запрашивает первоначальный автор.
Ответ 4
Я использую 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();
}
Ответ 5
Просто нужно было реализовать сравнение двух экземпляров сущностей, пересмотренных с помощью Hibernate Envers. Я начал писать свои собственные, но нашел следующую структуру.
https://github.com/SQiShER/java-object-diff
Вы можете сравнить два объекта одного типа и отображать изменения, добавления и удаления. Если изменений нет, объекты равны (теоретически). Аннотации предоставляются для геттеров, которые следует игнорировать во время проверки. Работа с кадрами имеет гораздо более широкие области применения, чем проверка равенства, то есть я использую для создания журнала изменений.
Его производительность в порядке, при сравнении объектов JPA обязательно отключайте их от диспетчера сущности.
Ответ 6
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);
Ответ 7
Пример вашего Linked List не так сложно обрабатывать. Когда код перемещается по двум объектным графам, он посещает объекты в Set или Map. Перед тем как перейти на другую ссылку на объект, этот набор проверяется, чтобы проверить, прошел ли объект уже пройден. Если это так, не нужно идти дальше.
Я согласен с человеком выше, который сказал, что использует LinkedList (например, Stack, но без синхронизированных методов на нем, поэтому он быстрее). Идеальным решением является перемещение графа объекта с использованием стека при использовании отражения для получения каждого поля. Написанный один раз, этот "внешний" equals() и "внешний" hashCode() - это то, что должны вызывать все методы equals() и hashCode(). Никогда еще вам не нужен метод equals() клиента.
Я написал немного кода, который пересекает полный граф объектов, указанный в Google Code. См. Json-io (http://code.google.com/p/json-io/). Он сериализует граф объектов Java в JSON и десериализуется из него. Он обрабатывает все объекты Java, с или без общих конструкторов, Serializeable или not Serializable и т.д. Этот же код обхода будет основой для внешней "equals()" и внешней реализации hashcode(). Btw, JsonReader/JsonWriter (json-io) обычно быстрее, чем встроенный объект ObjectInputStream/ObjectOutputStream.
Этот JsonReader/JsonWriter можно использовать для сравнения, но это не поможет с hashcode. Если вы хотите универсальный hashcode() и equals(), ему нужен собственный код. Возможно, я смогу это сделать с помощью общего посетителя графа. Посмотрим.
Другие соображения - статические поля - это легко - они могут быть пропущены, потому что все экземпляры equals() будут иметь одинаковое значение для статических полей, поскольку статические поля являются общими для всех экземпляров.
Что касается переходных полей - это будет выбираемый вариант. Иногда вам может потребоваться, чтобы переходные процессы не учитывали другие моменты. "Иногда вы чувствуете себя орехом, иногда вы этого не делаете".
Вернитесь к проекту json-io (для моих других проектов), и вы найдете внешний проект equals()/hashcode(). У меня пока нет имени, но это будет очевидно.
Ответ 8
Если ваши объекты реализуют 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);
}
}
Ответ 9
Переопределить Метод equals()
Вы можете просто переопределить метод equals() класса с помощью EqualsBuilder.reflectionEquals(), как описано здесь:
public boolean equals(Object obj) {
return EqualsBuilder.reflectionEquals(this, obj);
}
Ответ 10
Я думаю, вы знаете это, но в теории вы должны всегда переопределять .equals, чтобы утверждать, что два объекта действительно равны. Это означало бы, что они проверяют переопределенные методы .equals на своих членах.
Вот что такое .equals определено в Object.
Если бы это было сделано последовательно, у вас не было бы проблем.
Ответ 11
Гарантией прекращения такого глубокого сравнения может быть проблема. Что должны делать следующие? (Если вы реализуете такой компаратор, это создаст хороший unit test.)
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));
Ответ 12
У Hamcrest есть Matcher samePropertyValuesAs. Но он опирается на Конвенцию JavaBeans (использует геттеры и сеттеры). Если объекты, которые должны быть сопоставлены, не имеют геттеров и сеттеров для своих атрибутов, это не сработает.
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 - с геттерами и сеттерами
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;
}
}
Ответ 13
Я думаю, что самым легким решением, вдохновленным решением Ray Hulha, является сериализация объекта, а затем глубокое сравнение исходного результата.
Сериализация может быть байтом, json, xml или простой toString и т.д. ToString кажется более дешевым. Lombok генерирует для нас бесплатный простой настраиваемый 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());
}
}
Ответ 14
Apache дает вам что-то, конвертирует оба объекта в строку и сравнивает строки, но вам нужно переопределить toString()
obj1.toString().equals(obj2.toString())
Переопределить 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--;
}
}
}}