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.

Code in GitHub

Get the code for this tutorial using the links below.

Project Repo
Download code for this step
Main class for this step
Dependencies

This is a list of recommended tutorials or courses that might be useful before starting this one.

Contents
Welcome to the Course!
Course Introduction
Chapter 1 - Building Blocks
Quick introduction to Java Variables Classes And Objects Class Example - Defining a class Object Examples - Creating instances Java Application Example - Running our first app Accessing class members - The dot operator Packages - Organizing the code
Chapter 2 - Primitives and Operators
Primitives Arithmetic Operators Assignment Operator Unary Operators Equality and Relational Operators Conditional Operators
Chapter 3 - Statements and Control Flow
Expressions Statements If-Then Statement If-Then-Else Statement More If Statements Switch Statement While and Do-While Statements For Statement Branching Statements Exception Handling
Chapter 4 - Code Example
Example Project - A Simple Vending Machine Adding money Delivering Items Giving Change
Chapter 5 - Classes and Interfaces
Introduction Access Level Modifiers Class Declaration - Class, Methods and Fields Class Declaration - Constructors Inheritance Basics Inheritance - Constructors Inheritance - Methods and Fields Polymorphism Abstract Classes and Methods Interfaces Static Class Members Class Composition Final Classes and Class Members Generic Classes
Chapter 6 - Base Object Behaviors
Introduction Type Comparison Type Casting Object Equality - The Contract Object Equality - Common Pitfalls Object String Representation Garbage Collection Object Comparison Primitive Wrappers and Autoboxing
Chapter 7 - Data Structures
Introduction Arrays - Declaration and Creation Arrays - Basic Operations Core Collection Interfaces List and ArrayList - Basic Operations ArrayList Internals Introduction to Hash Tables Map and HashMap - Basic Operations Set and HashSet - Basic Operations
Chapter 8 - Anonymous classes and lambdas
Introduction Filtering a List Anonymous Classes Lambdas Built-in Functional Interfaces
Chapter 9 - Streams
Introduction Creating Streams Intermediate Operations Terminal Operations