Существует ли утилита отражения Java для глубокого сравнения двух объектов? - PullRequest
87 голосов
/ 19 сентября 2009

Я пытаюсь написать модульные тесты для множества операций clone() внутри большого проекта, и мне интересно, существует ли где-нибудь существующий класс, способный принимать два объекта одного типа, делая глубокий сравнение, и сказать, если они идентичны или нет?

Ответы [ 15 ]

1 голос
/ 29 июня 2015

Hamcrest имеет Matcher samePropertyValuesAs . Но он опирается на JavaBeans Convention (использует геттеры и сеттеры). Если сравниваемые объекты не имеют методов получения и установки для своих атрибутов, это не будет работать.

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

Пользовательский бин - с геттерами и сеттерами

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

}
1 голос
/ 25 марта 2011

Ваш пример со связанным списком не так сложен в обращении. Когда код пересекает два графа объектов, он помещает посещенные объекты в набор или карту. Прежде чем перейти к другой ссылке на объект, этот набор проверяется, чтобы убедиться, что объект уже пройден. Если это так, нет необходимости идти дальше.

Я согласен с вышеупомянутым человеком, который сказал использовать LinkedList (как Stack, но без синхронизированных методов, поэтому он быстрее). Обход графа объекта с помощью стека и использование отражения для получения каждого поля является идеальным решением. Написанный один раз, этот «внешний» equals () и «внешний» hashCode () - это то, что должны вызывать все методы equals () и hashCode (). Никогда больше вам не нужен метод equals () клиента.

Я написал немного кода, который просматривает полный граф объектов, указанный в Google Code. См. Json-io (http://code.google.com/p/json-io/).. Он сериализует граф объектов Java в JSON и десериализуется из него. Он обрабатывает все объекты Java с открытыми конструкторами или без них, Сериализуемый или не Сериализуемый и т. Д. Этот же код обхода будет основой для внешняя реализация "equals ()" и внешняя реализация "hashcode ()". Кстати, JsonReader / JsonWriter (json-io) обычно быстрее, чем встроенный ObjectInputStream / ObjectOutputStream.

Этот JsonReader / JsonWriter можно использовать для сравнения, но он не поможет с хэш-кодом. Если вам нужен универсальный hashcode () и equals (), ему нужен собственный код. Я могу быть в состоянии осуществить это с обычным посетителем графика. Посмотрим.

Другие соображения - статические поля - это просто - их можно пропустить, потому что все экземпляры equals () будут иметь одинаковое значение для статических полей, так как статические поля являются общими для всех экземпляров.

Что касается переходных полей - это будет выбор по выбору. Иногда вы можете захотеть, чтобы переходные процессы не учитывались в других случаях. «Иногда ты чувствуешь себя как орешек, иногда нет».

Вернитесь к проекту json-io (для других моих проектов), и вы найдете внешний проект equals () / hashcode (). У меня пока нет названия, но это будет очевидно.

0 голосов
/ 26 августа 2017

Я думаю, что самое простое решение, основанное на решении Рэя Хулхи , - это сериализация объекта и затем глубокое сравнение необработанного результата.

Сериализация может быть байтовой, 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());
    }
}   
0 голосов
/ 30 сентября 2010

Может возникнуть проблема с гарантией остановки для такого глубокого сравнения. Что должно делать следующее? (Если вы внедрите такой компаратор, это будет хорошим модульным тестом.)

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));
0 голосов
/ 30 сентября 2009

Полагаю, вы это знаете, но теоретически вы должны всегда переопределять .equals, чтобы утверждать, что два объекта действительно равны.Это будет означать, что они проверяют переопределенные методы .equals на своих членах.

Именно так .equals определяется в Object.

Если бы это было сделано последовательно, у вас не было быпроблема.

...