¿Existe una biblioteca Java que pueda "diferenciar" dos Objetos?

86

¿Existe una biblioteca de utilidades de Java que sea análoga al programa diff de Unix, pero para Objects? Estoy buscando algo que pueda comparar dos objetos del mismo tipo y generar una estructura de datos que represente las diferencias entre ellos (y pueda comparar de forma recursiva diferencias en variables de instancia). Estoy no en busca de una implementación en Java de un diff texto. También estoy no en busca de ayuda con la forma de utilizar la reflexión para hacer esto.

La aplicación que mantengo tiene una implementación frágil de esta funcionalidad que tenía algunas opciones de diseño deficientes y que necesita ser reescrita, pero sería aún mejor si pudiéramos usar algo disponible.

Aquí hay un ejemplo del tipo de cosas que estoy buscando:

SomeClass a = new SomeClass();
SomeClass b = new SomeClass();

a.setProp1("A");
a.setProp2("X");

b.setProp1("B");
b.setProp2("X");

DiffDataStructure diff = OffTheShelfUtility.diff(a, b);  // magical recursive comparison happens here

Después de la comparación, la utilidad me diría que "prop1" es diferente entre los dos objetos y "prop2" es el mismo. Creo que es más natural que DiffDataStructure sea un árbol, pero no voy a ser exigente si el código es confiable.

Kaypro II
fuente
8
verifique esto, code.google.com/p/jettison
denolk
13
Esto no es un duplicado de "¿Cómo probar la igualdad de gráficos de objetos complejos?" Estoy buscando una biblioteca, no un consejo sobre cómo hacerlo. Además, me interesa el delta, no si son iguales o no.
Kaypro II
1
Mire github.com/jdereg/java-util que tiene utilidades GraphComparator. Esta clase generará una lista de delta entre gráficos de objetos. Además, puede fusionar (aplicar) un delta a un gráfico. Efectivamente es List <Delta> = GraphComparator (rootA, rootB). Además de GraphComparator.applyDelta (rootB, List <Delta>).
John DeRegnaucourt
Sé que no desea utilizar la reflexión, pero esa es definitivamente la forma más fácil / sencilla de implementar algo como esto
RobOhRob

Respuestas:

42

Puede que sea un poco tarde, pero yo estaba en la misma situación que tú y terminé creando mi propia biblioteca para exactamente tu caso de uso. Como me vi obligado a encontrar una solución, decidí publicarla en Github, para ahorrarles a los demás el trabajo duro. Puede encontrarlo aquí: https://github.com/SQiShER/java-object-diff

--- Editar ---

Aquí hay un pequeño ejemplo de uso basado en el código de OP:

SomeClass a = new SomeClass();
SomeClass b = new SomeClass();

a.setProp1("A");
a.setProp2("X");

b.setProp1("B");
b.setProp2("X");

DiffNode diff = ObjectDifferBuilder.buildDefault().compare(a, b);

assert diff.hasChanges();
assert diff.childCount() == 1;
assert diff.getChild('prop1').getState() == DiffNode.State.CHANGED;
SQiShER
fuente
1
¿Puedo señalarle esta publicación que muestra un caso de uso de java-object-diff? stackoverflow.com/questions/12104969/… ¡ Muchas gracias por esta biblioteca útil!
Matthias Wuttke
Eché un vistazo a tu publicación y escribí una respuesta que resultó ser demasiado larga para publicarla como comentario, así que aquí está la "esencia": gist.github.com/3555555
SQiShER
2
Ordenado. Terminé escribiendo mi propia implementación también. No estoy seguro de tener la oportunidad de explorar realmente su implementación, pero tengo curiosidad por saber cómo maneja las Listas. Terminé manejando todas las diferencias de colección como diferencias de Maps (definiendo las claves de diferentes maneras dependiendo de si es una lista, conjunto o mapa; luego usando operaciones de conjunto en el conjunto de claves). Sin embargo, ese método es demasiado sensible a los cambios de índice de lista. No resolví ese problema porque no lo necesitaba para mi aplicación, pero tengo curiosidad por saber cómo lo manejaste (más como un text-diff, supongo).
Kaypro II
2
Trajiste un buen punto. Las colecciones son realmente difíciles de manejar. Especialmente si apoyas la fusión, como yo. Resolví el problema usando identidades de objeto para detectar si un elemento se ha agregado, cambiado o eliminado (a través de hashCode, es igual y contiene). Lo bueno de esto es que puedo tratar todas las colecciones por igual. Lo malo es que no puedo manejar correctamente (por ejemplo) ArrayLists que contienen el mismo objeto varias veces. Me encantaría agregar soporte para eso, pero parece ser bastante complicado y, afortunadamente, nadie lo pidió todavía. :-)
SQiShER
Daniel, gracias por tu comentario! Tienes razón, reconstruir el objeto original a partir de las diferencias es difícil, pero en mi caso esto no es demasiado importante. Originalmente, almacené versiones anteriores de mi objeto en la base de datos, como usted recomienda. El problema era que la mayoría de las partes de los documentos grandes no cambiaban, había muchos datos duplicados. Es por eso que comencé a buscar alternativas y encontré java-object-diff. También experimenté dificultades con colecciones / mapas y modifiqué mis métodos "iguales" para verificar la igualdad de la base de datos primaria (y no del objeto completo), esto ayudó.
Matthias Wuttke
39

http://javers.org es una biblioteca que hace exactamente lo que necesita: tiene métodos como comparar (Object leftGraph, Object rightGraph) que devuelve el objeto Diff. Diff contiene una lista de cambios (ReferenceChange, ValueChange, PropertyChange) p. Ej.

given:
DummyUser user =  dummyUser("id").withSex(FEMALE).build();
DummyUser user2 = dummyUser("id").withSex(MALE).build();
Javers javers = JaversTestBuilder.newInstance()

when:
Diff diff = javers.compare(user, user2)

then:
diff.changes.size() == 1
ValueChange change = diff.changes[0]
change.leftValue == FEMALE
change.rightValue == MALE

Puede manejar ciclos en gráficos.

Además, puede obtener una instantánea de cualquier objeto gráfico. Javers tiene serializadores y deserializadores JSON para realizar instantáneas y cambios para que pueda guardarlos fácilmente en la base de datos. Con esta biblioteca, puede implementar fácilmente un módulo de auditoría.

Paweł Szymczyk
fuente
1
esto no funciona. ¿Cómo se crea una instancia de Javers?
Ryan Vettese
2
Javers tiene una excelente documentación que lo explica todo: javers.org
Paweł Szymczyk
2
Intenté usarlo pero es solo para Java> = 7. ¡¡¡Chicos, todavía hay gente trabajando en proyectos en Java 6 !!!
jesantana
2
¿Qué pasa si los dos objetos que comparo tienen una lista de objetos internamente y también veré esas diferencias? ¿Cuál es el nivel de jerarquía que los javers podrían comparar?
Deepak
1
Sí, verá diferencias en los objetos internos. Por el momento no existe un umbral para el nivel de jerarquía. Los Javers irán tan profundo como sea posible, la limitación es el tamaño de la pila.
Paweł Szymczyk
15

Sí, la biblioteca java-util tiene una clase GraphComparator que comparará dos gráficos de objetos Java. Devuelve la diferencia como una lista de deltas. GraphComparator también le permite fusionar (aplicar) los deltas. Este código solo tiene dependencias del JDK, no de otras bibliotecas.

John DeRegnaucourt
fuente
12
Parece una buena biblioteca, debe mencionar que es un colaborador
Christos
3

Toda la biblioteca de Javers tiene soporte solo para Java 7, estaba en una situación ya que quería que esto se usara para un proyecto de Java 6, así que tomé la fuente y cambié de una manera que funciona para Java 6 a continuación es el código github .

https://github.com/sand3sh/javers-forJava6

Enlace de jar: https://github.com/sand3sh/javers-forJava6/blob/master/build/javers-forjava6.jar

Solo he cambiado las conversiones de conversión inherentes '<>' compatibles con Java 7 a la compatibilidad con Java 6.No garantizo que todas las funcionalidades funcionarán, ya que he comentado algunos códigos innecesarios para mí, funciona para todas las comparaciones de objetos personalizados.

Sandesh
fuente
2

También puede echar un vistazo a la solución de Apache. La mayoría de los proyectos ya lo tienen en su classpath ya que es parte de commons-lang.

Verifique la diferencia para campos específicos:
http://commons.apache.org/proper/commons-lang/javadocs/api-3.9/org/apache/commons/lang3/builder/DiffBuilder.html

Verifique la diferencia usando la reflexión:
http://commons.apache.org/proper/commons-lang/javadocs/api-3.9/org/apache/commons/lang3/builder/ReflectionDiffBuilder.html

John Blackwell
fuente
1

Quizás esto ayude, dependiendo de dónde use este código, podría ser útil o problemático. Probé este código.

    /**
 * @param firstInstance
 * @param secondInstance
 */
protected static void findMatchingValues(SomeClass firstInstance,
        SomeClass secondInstance) {
    try {
        Class firstClass = firstInstance.getClass();
        Method[] firstClassMethodsArr = firstClass.getMethods();

        Class secondClass = firstInstance.getClass();
        Method[] secondClassMethodsArr = secondClass.getMethods();


        for (int i = 0; i < firstClassMethodsArr.length; i++) {
            Method firstClassMethod = firstClassMethodsArr[i];
            // target getter methods.
            if(firstClassMethod.getName().startsWith("get") 
                    && ((firstClassMethod.getParameterTypes()).length == 0)
                    && (!(firstClassMethod.getName().equals("getClass")))
            ){

                Object firstValue;
                    firstValue = firstClassMethod.invoke(firstInstance, null);

                logger.info(" Value "+firstValue+" Method "+firstClassMethod.getName());

                for (int j = 0; j < secondClassMethodsArr.length; j++) {
                    Method secondClassMethod = secondClassMethodsArr[j];
                    if(secondClassMethod.getName().equals(firstClassMethod.getName())){
                        Object secondValue = secondClassMethod.invoke(secondInstance, null);
                        if(firstValue.equals(secondValue)){
                            logger.info(" Values do match! ");
                        }
                    }
                }
            }
        }
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
}
r0ast3d
fuente
2
Gracias, pero ya tenemos un código que recorre el gráfico de objetos usando listas de reflexión de los cambios. Esta pregunta no se trata de cómo hacerlo, se trata de tratar de evitar reinventar la rueda.
Kaypro II
Reinventar la rueda no está mal si la reinvención ya sucedió. (IE: La rueda reinventada ya estaba pagada.)
Thomas Eding
1
Estoy de acuerdo contigo, pero en este caso el código reinventado es un desastre inmaterial.
Kaypro II
3
Estaría comprobando Fieldsmétodos no. Los métodos pueden ser cualquier cosa, como getRandomNumber().
Bohemio
-3

Un enfoque más simple para decirle rápidamente si dos objetos son diferentes sería usar la biblioteca apache commons

    BeanComparator lastNameComparator = new BeanComparator("lname");
    logger.info(" Match "+bc.compare(firstInstance, secondInstance));
r0ast3d
fuente
Eso no funciona para mí porque necesito saber qué cambió, no solo que hubo un cambio en alguna parte.
Kaypro II