Introduction
When we want a class to be diffed, i.e., compared with another instance of the same class and find the differences among the values of all fields, we can use the Apache Commons Lang Diffable for this. Let us dive straight into it.
Apache Commons Lang Diffable
The Apache Commons Lang has an interface called Diffable which is a functional interface with one method called diff(). A class which implements the Diffable interface can be compared with other objects (of the same type) for differences. By differences, I mean the value differences of various fields between the two instances.
Think of a Diffable similar to a Comparable. When a class implements the Comparable interface, we implement the compareTo method. A Comparable means that we can compare one instance (of that class) with another (to impose an ordering). Similarly, when a class implements the Diffable interface, we can say it is ‘diffable’. In other words, we can diff two instances of the class to get the list of differences between them. We get it by calling the diff method which returns a DiffResult which contains the list of Diffs. We can build a DiffResult using a DiffBuilder. So, the key types to know are:
- Diffable
- DiffResult
- Diff
- DiffBuilder
The Diffable interface
As we’ve just seen, a Diffable is a functional interface with one method, as shown below. It is parameterized with type parameter T.
@FunctionalInterface
public interface Diffable<T> {
DiffResult<T> diff(T obj);
}
The diff method takes an instance of the same type (T) and returns a DiffResult<T>. The DiffResult has several methods (which we’ll see soon) to get the resultant differences.
The DiffResult class
A DiffResult<T> exposes or returns the collection of the differences between two Diffable objects. It implements an Iterable<Diff<?>> as shown below.
public class DiffResult<T> implements Iterable<Diff<?>> {
//methods
}
The DiffResult has the following methods:
- getDiffs() - It returns the list of the differences (
List<Diff<?>>
). - getLeft() and getRight() - These return the left and the right objects used when performing the diff, respectively. When we do
obj1.diff(obj2)
, obj1 is the left object and obj2 is the right object. - getNumberOfDiffs() - Returns the number of differences between the two objects.
- iterator() - Returns an iterator over the diffs (
Iterator<Diff<?>>
).
Other than these, the toString() of the DiffResult returns a very well formatted string capturing all the differences (i.e., fields that differ between the objects).
We can use the ToStringStyle to configure the format of the differences. We will look at these later in the post.
The Diff class
Diff<T> is an abstract class which extends the Apache Commons Lang Pair class, whose left and right are the same type (T).
public abstract class Diff<T> extends Pair<T, T> {
...
}
A Diff contains the differences between two Diffable instances. A Diff class has the following methods:
- getFieldName() - Returns the name of the field
- getType() - Returns the type of the field
It inherits the getLeft() and getRight() methods from the Pair. And since a Pair extends the Map.Entry, we also have getKey() and getValue() methods as well. Internally, the getKey() delegates or calls the getLeft() method and the getValue() calls the getRight() method.
The Diff instance’s toString has the below format:
[fieldname: left-value, right-value]
The DiffBuilder class
We use a DiffBuilder<T> to build an instance of DiffResult. A DiffBuilder implements Builder<DiffResult<T>> as shown below.
A Builder<T> is a functional interface whose build() method returns an instance of T. Hence the build() method of a DiffBuilder returns DiffResult<T>.
public class DiffBuilder<T> implements Builder<DiffResult<T>> {
...
}
@FunctionalInterface
public interface Builder<T> {
T build();
}
We use a DiffBuilder to build a DiffResult when implementing the Diffable#diff method.
Apache Commons Lang Diffable - Class hierarchy
Having looked at the four key interfaces/classes viz., Diffable, DiffResult, Diff, and DiffBuilder, here’s the class diagram of those.
Implementing the Diffable interface
Let us create a class and implement the Diffable interface. As shown below, we have a Student class where each student has a name, an age and a list of courses the Student has taken up.
Since we are implementing Diffable<Student>
, we have to implement the diff(Student) method. We use the DiffBuilder to build and return a DiffResult<Student>
.
public class Student implements Diffable<Student> {
private final String name;
private final int age;
private final List<String> courses;
public Student(String name, int age, List<String> courses) {
this.name = name;
this.age = age;
this.courses = List.copyOf(courses);
}
@Override
public DiffResult<Student> diff(Student otherStudentObj) {
return new DiffBuilder<>(this, otherStudentObj, ToStringStyle.DEFAULT_STYLE)
.append("name", this.name, otherStudentObj.name)
.append("age", this.age, otherStudentObj.age)
.append("courses", this.courses, otherStudentObj.courses)
.build();
}
}
Here, we pass three arguments to the constructor of the DiffBuilder class. The first two arguments are the lhs and the rhs object. In other words, the lhs is the this object and rhs is the object to diff against. In the last argument, we tell it to use the default style when outputting the DiffResult.
Once we have an instance of DiffBuilder, we use the append() method to add the fields of the Student class. It will use only the fields we add using the append() method to compare the two instances. Let’s say if we don’t include the courses field, then it will diff two Student objects only using the name and the age fields.
The append() method takes three arguments. The first is the name of the field. It uses this when outputting the Diff. The second and third are the left hand (lhs) and the right hand (rhs) values. There are several overloaded append() methods for taking various types of values.
Finally, we call the build() method to get back an instance of DiffResult.
Comparing two objects for differences
Let us build two Student objects and compare them by calling the diff() method.
Student student1 = new Student("Adam", 21, List.of("CS", "Math"));
Student student2 = new Student("Scott", 21, List.of("CS", "Science"));
DiffResult<Student> diffResult = student1.diff(student2);
System.out.println(diffResult);
Here, we have two Students (Adam and Scott). We call the diff() method on the first student passing the second Student instance as an argument. Then, we print the returned DiffResult. The default toString() implementation builds a string which shows the diff as shown below (i.e., the result of running the above code). The toString() of a DiffResult would be a single string. Here I’ve added line breaks to make it more readable.
com.javadevcentral.commons.lang3.diff.Student@37f8bb67[name=Adam,courses=[CS, Math]]
differs from
com.javadevcentral.commons.lang3.diff.Student@49c2faae[name=Scott,courses=[CS, Science]]
The Student class is in the Java package namespace com.javadevcentral.commons.lang3.diff
. The DiffResult says that the two Student objects differ in their name and courses values. It also shows the actual values of these two fields. Since both Students have the same value for the age field, it is not part of the diff.
The DiffResult here is using ToStringStyle.DEFAULT_STYLE
to print the differences. We’ll look at other styles as part of ToStringStyle shortly.
Comparing two equal objects for differences
If we pass two Students with same values for all the fields, then printing the DiffResult will return an empty string.
Student student1 = new Student("Adam", 21, List.of("CS", "Math"));
Student student2 = new Student("Adam", 21, List.of("CS", "Math"));
DiffResult<Student> diffResult = student1.diff(student2);
System.out.println(diffResult); //""
Methods on the DiffResult class
We’ve looked at the default toString() representation of a DiffResult. Let us now call the other methods on a DiffResult.
getNumberOfDiffs, getLeft and getRight
The getNumberOfDiffs() method returns the number of diffs (2 in this example).
Student student1 = new Student("Adam", 21, List.of("CS", "Math"));
Student student2 = new Student("Scott", 21, List.of("CS", "Science"));
DiffResult<Student> diffResult = student1.diff(student2);
System.out.println(diffResult.getNumberOfDiffs()); //2
The getLeft() and getRight() methods return the left and right Student instances.
System.out.println(diffResult.getLeft());
System.out.println(diffResult.getRight());
If we have a reasonable toString() implemented for Student as shown below,
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", courses=" + courses +
'}';
}
The result of getLeft() and getRight() would be,
Student{name='Adam', age=21, courses=[CS, Math]}
Student{name='Scott', age=21, courses=[CS, Science]}
DiffResult#getDiffs
To get the list of all diffs from a DiffResult, we can call the getDiffs() method. It returns a List<Diff<?>>
.
List<Diff<?>> diffs = diffResult.getDiffs();
System.out.println(diffs); //[[name: Adam, Scott], [courses: [CS, Math], [CS, Science]]]
The default toString() implementation of a Diff is shown below. It prints the name of the field followed by the left and right values. Hence, in the above output, we see the name field and the courses field followed by the values.
public final String toString() {
return String.format("[%s: %s, %s]", fieldName, getLeft(), getRight());
}
As seen earlier, the Diff class has methods which return these values. Hence, we can iterate over the Diffs and call them as required.
In the below code, we iterate over the diffs and print the details like the:
- Name of the field
- Its type
- The left value
- The right value
for (int i = 0; i < diffs.size(); i++) {
Diff<?> diff = diffs.get(i);
System.out.println("Diff #" + (i + 1));
System.out.println("Field name: " + diff.getFieldName());
System.out.println("Type : " + diff.getType());
System.out.println("Left value: " + diff.getLeft());
System.out.println("Right value: " + diff.getRight());
System.out.println();
}
Running this code, we get,
Diff #1
Field name: name
Type : class java.lang.Object
Left value: Adam
Right value: Scott
Diff #2
Field name: courses
Type : class java.lang.Object
Left value: [CS, Math]
Right value: [CS, Science]
DiffResult#getToStringStyle
The getToStringStyle() method returns the style used by the toString() method of the DiffResult to output the diff.
System.out.println(diffResult.getToStringStyle());
Prints,
org.apache.commons.lang3.builder.ToStringStyle$DefaultToStringStyle@27fa135a
Since ToStringStyle class doesn’t have a default toString() implementation, we get a string representation from the default implementation. It has the name of the ToStringStyle used as part of the class name (DefaultToStringStyle in this example).
DiffResult#iterator
We saw that the getDiffs() method returns a List<Diff<?>>
. We can also cal the iterator() method to get back an Iterator<Diff<?>>
.
Iterator<Diff<?>> diffIterator = diffResult.iterator();
while (diffIterator.hasNext()) {
System.out.println(diffIterator.next());
}
Prints,
[name: Adam, Scott]
[courses: [CS, Math], [CS, Science]]
DiffResult - toString with ToStringStyle
We pass an instance of ToStringStyle when creating the DiffBuilder instance. Then, the DiffBuilder will pass that ToStringStyle to the DiffResult and the DiffResult will use that style when presenting the diffs.
If we already have a DiffResult and want to present the diffs in a different style(s), it is not easy to dynamically change the value of ToStringStyle passed to the DiffBuilder constructor to build a new DiffResult. Instead, we can use the overloaded toString() method on a DiffResult which takes a ToStringStyle and returns a String which represents the differences contained within this DiffResult built using the passed ToStringStyle Basically, they are the same diffs represented or presented in a different style.
The ToStringStyle class has public static fields which exposes several styles. They are:
- DEFAULT_STYLE
- JSON_STYLE
- MULTI_LINE_STYLE
- NO_CLASS_NAME_STYLE
- NO_FIELD_NAMES_STYLE
- SHORT_PREFIX_STYLE
- SIMPLE_STYLE
Let us use the same example as before.
Student student1 = new Student("Adam", 21, List.of("CS", "Math"));
Student student2 = new Student("Scott", 21, List.of("CS", "Science"));
DiffResult<Student> diffResult = student1.diff(student2);
To format the diffs using any of the above styles, we just have to pass the appropriate ToStringStyle instance to the toString() method.
System.out.println(diffResult.toString(ToStringStyle.JSON_STYLE));
System.out.println(diffResult.toString(ToStringStyle.MULTI_LINE_STYLE));
//etc.,
Let us use the toString() for each of the supported ToStringStyle and look at the outputs.
JSON Style:
The JSON style outputs the left and right instance’s data as a JSON as shown below.
{"name":"Adam","courses":["CS","Math"]} differs from {"name":"Scott","courses":["CS","Science"]}
Multi Line Style:
The Multi Line Style gives a more readable diff result by having one field in one line.
com.javadevcentral.commons.lang3.diff.Student@2cdf8d8a[
name=Adam
courses=[CS, Math]
] differs from com.javadevcentral.commons.lang3.diff.Student@30946e09[
name=Scott
courses=[CS, Science]
]
No Class Names Style:
By using this style of diff, we get only the field names and the values (without the class name).
[name=Adam,courses=[CS, Math]] differs from [name=Scott,courses=[CS, Science]]
No Field Names Style:
As the name indicates, this presents the diff without the field names.
com.javadevcentral.commons.lang3.diff.Student@2cdf8d8a[Adam,[CS, Math]] differs from com.javadevcentral.commons.lang3.diff.Student@30946e09[Scott,[CS, Science]]
Short Prefix Style:
The short prefix style is similar to the default style, but it replaces the default toString() representation of the object (using the full class name and hash code) with the simple class name.
Student[name=Adam,courses=[CS, Math]] differs from Student[name=Scott,courses=[CS, Science]]
Simple Style:
The simple style just uses the field values alone in the diffs output.
Adam,[CS, Math] differs from Scott,[CS, Science]
DiffBuilder with testTriviallyEqual flag
We looked at the constructor of the DiffBuilder class taking the left, right objects to diff and an instance of ToStringStyle. There is another overloaded method which takes a boolean flag named testTriviallyEqual.
When set to true, it checks if the lhs and rhs instances are the same (identity equality) or equal. If yes, then all the calls to append(fieldName, lhs, rhs) methods will abort without creating a diff (even if the two passed values are different). By default, it is set as true.
If in the Student class we implement the toString() and hashCode() methods comparing only the name of the Student, i.e., two Student instances are equal if they have the same name.
public class Student implements Diffable<Student> {
private final String name;
private final int age;
private final List<String> courses;
public Student(String name, int age, List<String> courses) {
this.name = name;
this.age = age;
this.courses = List.copyOf(courses);
}
@Override
public DiffResult<Student> diff(Student otherStudentObj) {
return new DiffBuilder<>(this, otherStudentObj, ToStringStyle.DEFAULT_STYLE)
.append("name", this.name, otherStudentObj.name)
.append("age", this.age, otherStudentObj.age)
.append("courses", this.courses, otherStudentObj.courses)
.build();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(name);
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", courses=" + courses +
'}';
}
}
We build two Student instances with the same name. Since student1.equals(student2)
is true, and since testTriviallyEqual is true as well, it will not compare the individual fields and will consider the objects as equal with no diffs (even if there are differences in the age and courses). Hence, the diffResult will be an empty string.
Student student1 = new Student("Adam", 21, List.of("CS", "Math"));
Student student2 = new Student("Adam", 25, List.of("CS", "Science"));
DiffResult<Student> diffResult = student1.diff(student2);
System.out.println(diffResult.toString(ToStringStyle.SHORT_PREFIX_STYLE)); //""
However, if we pass a value of false for testTriviallyEqual when creating the DiffBuilder, we will get the diffs as per the fields added through the append() methods.
@Override
public DiffResult<Student> diff(Student otherStudentObj) {
return new DiffBuilder<>(this, otherStudentObj, ToStringStyle.DEFAULT_STYLE, false)
.append("name", this.name, otherStudentObj.name)
.append("age", this.age, otherStudentObj.age)
.append("courses", this.courses, otherStudentObj.courses)
.build();
}
Now, the diffResult shows the diffs in the age and course fields.
Student[age=21,courses=[CS, Math]] differs from Student[age=25,courses=[CS, Science]]
Conclusion
This concludes the post on the Apache Commons Lang Diffable. We learnt about the Diffable interface and other related classes like DiffBuilder, DiffResult and Diff and how to implement the Diffable interface to make a class ‘diffable’.