Chapter 6 - Java for Beginners Course

Object Comparison

In the section on object equality we showed how we can establish if an object is equal or not to another object via the equals method. However, we can’t establish if an object is greater than, equal to, or less than another object.

For primitive types we know we can use the relational operators, for example, if we have two int variables x and y, we can check if x is less than y by doing x < y. However, in Java we can’t apply these relational operators to objects.

The Comparable interface

The standard way in Java to specify that a class offers a way to compare objects is by implementing the Comparable interface. By implementing this interface, the class is required to implement the following method:

int compareTo(T o);

where T is the type/class we’re marking as Comparable. The return type is an int value that has the following meaning:

  • a negative value means that this object is less than the object passed as a parameter

  • zero means that this object is equal to the object passed as a parameter

  • a positive value means that this object is greater than the object passed as a parameter

When a class implements the Comparable interface it is said to have a natural order as we can sort objects of this type using this method.

Let’s take a look at an example, where we’ll take the DictionaryEntry class from the previous sections and add this behavior to it.

Making our DictionaryEntry a Comparable type

The first thing we need to do is declare that our DictionaryEntry class implements the Comparable interface, and we do this as follows:

public class DictionaryEntry implements Comparable<DictionaryEntry> {
	//...
}

The type parameter we pass into the Comparable interface is <DictionaryEntry> as we want to compare our DictionaryEntry class to objects of the same DictionaryEntry type.

After declaring this, we now need to implement the compareTo method. For the purpose of this example, let’s assume we want to sort our DictionaryEntry objects alphabetically on the word they represent (as you’d find in a dictionary).

public class DictionaryEntry implements Comparable<DictionaryEntry> {
	private final String word;

	// ...

	@Override
	public int compareTo(DictionaryEntry o) {
	    return word.compareTo(o.word);
	}
}

In this case, we’re relying on the fact that word is of type String and that class also implements Comparable. Hence, we’re calling compareTo on the word field to compare it with the word that the other object represents.

The compareTo method in the String class in Java can be used to sort Strings in lexicographical order which is what we want for this example.

Let’s give this a go:

In the example code run the JavaObjectComparisonApp
DictionaryEntry alpha = new DictionaryEntry("alpha", "definition of alpha");
DictionaryEntry beta = new DictionaryEntry("beta", "definition of beta");

System.out.println("Alpha compared to beta: " + alpha.compareTo(beta));
System.out.println("Beta compared to alpha: " + beta.compareTo(alpha));

Output:

Alpha compared to beta: -1
Beta compared to alpha: 1

We can see in the output that when comparing alpha to beta we get a negative value, indicating that alpha is less than beta. We get the opposite result when comparing beta to alpha.

What if I want a different ordering/comparison?

In some cases, you’ll use classes that you don’t own, or that already define a natural order (as in, they already implement Comparable), but the order defined there isn’t what you’re after.

For example, our DictionaryEntry classes will sort the objects in lexicographical order, so if you had a list of DictionaryEntry objects that represented the words: bat, ant and ox, the natural order would result in ant, bat, ox.

However, let’s assume that you want to sort them by length and then, if the words have the same length, sort them in alphabetical order. In this case, the natural order in the DictionaryEntry won’t work. For the 3 words above, the order we’re after is: ox, ant, bat.

The Comparator interface

A second commonly used interface is the Comparator<T> interface that allows us to define an ordering of elements of a given type T. The main difference, is that the method you need to implement receives 2 objects to be compared to one another:

int compare(T o1, T o2);

The return value has a very similar meaning to the comparable interface:

  • a negative value means that the first object is less than the second object that were passed as parameters

  • zero means that both objects are equal

  • a positive value means that the first object is greater than the second object that were passed as parameters

Now, let’s implement a Comparator for the use case we discussed above. We’ll call this comparator a WordLengthComparator:

public class WordLengthComparator implements Comparator<DictionaryEntry> {
    @Override
    public int compare(DictionaryEntry word1, DictionaryEntry word2) {
        int lengthDifference = word1.getWord().length() - word2.getWord().length();
        if (lengthDifference == 0) {
            return word1.compareTo(word2);
        }
        return lengthDifference;
    }
}

The compare method is first checking the difference between the length of the words. Only if they are the same length (i.e. if the difference is 0), we rely on the natural order of the words (word1.compareTo(word2)). If not, we return the lengthDifference which will be negative if the first word is shorter than the second, or positive otherwise (which is what we want).

We can now use this WordLengthComparator to do exactly what we wanted for our second sorting example. Let’s give this a go:

In the example code run the JavaObjectComparatornApp
DictionaryEntry ant = new DictionaryEntry("ant", "definition of ant");
DictionaryEntry bat = new DictionaryEntry("bat", "definition of bat");
DictionaryEntry ox = new DictionaryEntry("ox", "definition of ox");

WordLengthComparator wordLengthComparator = new WordLengthComparator();

System.out.println("Ox compared to ant: " + wordLengthComparator.compare(ox, ant));
System.out.println("Ox compared to bat: " + wordLengthComparator.compare(ox, bat));
System.out.println("Ant compared to bat: " + wordLengthComparator.compare(ant, bat));

Output:

Ox compared to ant: -1
Ox compared to bat: -1
Ant compared to bat: -1

We can see that the word ox is considered to be less than ant and bat as expected, as it has a shorter length (we get a negative value for both comparisons).

Also, as ant and bat have the same length, they are sorted alphabetically and we get that ant is less than bat.