This tutorial helps you understand about object ordering in the Java programming language. You will be able to understand how sorting actually works with elements in collections and arrays, and more interestingly, you will be able to write your own code for sorting elements by the ways you want.

To understand object ordering properly, let’s see some examples where we use the utility class Collections to sort elements of a collection (or Arrays class to sort elements in an array):

  • Collections.sort(list): sorts a List collection.
  • Arrays.sort(array): sorts an array.

Throughout this lesson, we use the sort() method of the Collections class.

 

Example #1: Sorting a list of String objects 

List<String> names = Arrays.asList(
			"Tom", "Peter", "Alice", "Bob", "Sam",
			"Mary", "Jane", "Bill", "Tim", "Kevin");
System.out.println("Before sorting: " + names);
Collections.sort(names);
System.out.println("After sorting: " + names);

Output:

Before sorting: [Tom, Peter, Alice, Bob, Sam, Mary, Jane, Bill, Tim, Kevin]
After sorting: [Alice, Bill, Bob, Jane, Kevin, Mary, Peter, Sam, Tim, Tom]

In this example, the list names is sorted by alphabetic order of String.

 

Example #2: Sorting a list of Integer objects 

List<Integer> numbers = Arrays.asList(8, 2, 5, 1, 3, 4, 9, 6, 7, 10);
System.out.println("Before sorting: " + numbers);
Collections.sort(numbers);
System.out.println("After sorting: " + numbers);

Output:

Before sorting: [8, 2, 5, 1, 3, 4, 9, 6, 7, 10]
After sorting: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

Here, the integer numbers in the list numbers are sorted by alphanumeric order.

 

Example #3: Sorting a list of Date objects 

DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
List<Date> birthdays = new ArrayList<>();
birthdays.add(dateFormat.parse("2016-01-20"));
birthdays.add(dateFormat.parse("1998-12-03"));
birthdays.add(dateFormat.parse("2009-07-15"));
birthdays.add(dateFormat.parse("2012-04-30"));
System.out.println("Before sorting: ");
for (Date date : birthdays) {
	System.out.println(dateFormat.format(date));
}
Collections.sort(birthdays);
System.out.println("After sorting: ");
for (Date date : birthdays) {
	System.out.println(dateFormat.format(date));
}

Output:

Before sorting:
2016-01-20
1998-12-03
2009-07-15
2012-04-30
After sorting:
1998-12-03
2009-07-15
2012-04-30
2016-01-20

Here, the list birthdays is sorted by chronological order of its elements - objects of type Date.

From the 3 examples above, the collections are sorted by natural ordering of its elements:

- The natural ordering of String objects is alphabetic order.

- The natural ordering of Integer objects is alphanumeric order.

- The natural ordering of Date objects is chronological order.

Let’s dive into the concept of natural ordering.

 

1. Understanding Natural Ordering

Natural ordering is the default ordering of objects of a specific type when they are sorted in an array or a collection. The Java language provides the Comparable interface that allows us define the natural ordering of a class. This interface is declared as follows:

public interface Comparable<T> {
	public int compareTo(T object);
}

As you can see, this interface is parameterized (generics) and it has a single method compareTo() that allows two objects of a same type to be compared with each other. The important point here is the value returned by this method: an integer number indicates the comparison result of two objects. Remember these rules:

  • compare value = 0: two objects are equal.
  • compare value > 0: the first object (the current object) is greater than the second one.
  • compare value < 0: the first object is less than the second one.

Imagine that, when the objects are being sorted, their compareTo() methods are invoked to compare with other objects. And based on the compare value returned, the objects are sorted by natural ordering.

Classes whose objects used in collections or arrays should implement the Comparableinterface for providing the natural ordering of its objects when being sorted. Otherwise we will get an error at runtime.

A class that implements the Comparable interface is said to have class natural ordering. And the compareTo() method is called the natural comparison method.

In the above examples, we don’t have to write code to implement the Comparableinterface because the String, Integer and Date classes already implemented this interface. Hence we can sort a collection containing objects of these types.

Other wrapper types in Java are also comparable: Long, Double, Float, etc.

When we create our own type, we have to implement the Comparableinterface in order to have objects of our type eligible to be sorted in collections or arrays. Let’s see an example to understand how the Comparableinterface is used.

Let’s say we have the Employee class which is defined as shown below:

/**
 * Employee.java
 * @author www.codejava.net
 */
public class Employee {
	String firstName;
	String lastName;
	Date joinDate;
	public Employee(String firstName, String lastName) {
		this.firstName = firstName;
		this.lastName = lastName;
	}
	public String toString() {
		return firstName + " " + lastName;
	}
	// getters and setters
}

Add some employees to a list collection like this:

List<Employee> listEmployees = new ArrayList<>();
Employee employee1 = new Employee("Tom", "Eagar");
Employee employee2 = new Employee("Tom", "Smith");
Employee employee3 = new Employee("Bill", "Joy");
Employee employee4 = new Employee("Bill", "Gates");
Employee employee5 = new Employee("Alice", "Wooden");
listEmployees.add(employee1);
listEmployees.add(employee2);
listEmployees.add(employee3);
listEmployees.add(employee4);
listEmployees.add(employee5);

Try to sort this list:

Collections.sort(listEmployees);

We will get an error at runtime: no suitable method found for sort(List<Emmployee>)…

WHY?

It’s because the Employee class doesn’t implement the Comparable interface so the sort() method cannot compare the objects.

Now, let’s have the Employee class implements the Comparable interface, and we define the natural ordering is first name - last name, meaning the employees are sorted by first name first, then by last name. Here’s the updated version of the Employee class:

/**
 * Employee.java
 * Implementing Comparable
 * @author www.codejava.net
 */
public class Employee implements Comparable<Employee> {
	// fields...
	// constructors...
	// getters...
	// setters...
	// implement the natural comparison method:
	
	public int compareTo(Employee another) {
		int compareValue = this.firstName.compareTo(another.firstName);
		if (compareValue == 0) {
			return this.lastName.compareTo(another.lastName);
		}
		return compareValue;
	}
}

Look at how the compareTo() method is implemented here:

  • First, we compare the first name by using the String’s compareTo() method. We can safely use this method of the built-in types in Java: String, Date, Integer, Long, etc.

     

  • If two employees have same first name (compare value = 0), then we compare their last name. Finally the compare value is returned as per the contract of the Comparable interface.

Now, run this test code and observe the result:

System.out.println("Before sorting: " + listEmployees);
Collections.sort(listEmployees);
System.out.println("After sorting: " + listEmployees);

Output:

Before sorting: [Tom Eagar, Tom Smith, Bill Joy, Bill Gates, Alice Wooden]
After sorting: [Alice Wooden, Bill Gates, Bill Joy, Tom Eagar, Tom Smith]

Awesome! It works perfectly as we expected: the employees are sorted by their first name, and then last name.

 

Note #1:

We cannot compare objects of different types, e.g. a String object cannot be compared with an Integer object. As the compareTo() method enforces this rule, we can only compare objects of the same type. If we add objects of different types to a collection and sort it, we will get ClassCastException.

 

Note #2:

If we want to reverse the natural ordering, simply swap the objects being compared in the compareTo() method. For example, the following implementation sorts employees by their first name into descending order:

public int compareTo(Employee another) {
	return another.firstName.compareTo(this.firstName);
}

In case we use a sorted collection i.e. TreeSet, we don’t have to use the Collections.sort() utility method, as a TreeSet sorts its elements by their natural ordering. The following example demonstrates how to use a TreeSet to sort Strings:

Set<String> setNames = new TreeSet<>();
setNames.addAll(Arrays.asList("Tom", "Peter", "Alice", "Bob", "Sam",
			      "Mary", "Jane", "Bill", "Tim", "Kevin"));
System.out.println(setNames);

Output:

[Alice, Bill, Bob, Jane, Kevin, Mary, Peter, Sam, Tim, Tom]

Similarly, we can sort the Employee objects using a TreeSet like this:

Set<Employee> setEmployees = new TreeSet<>();
Employee employee1 = new Employee("Tom", "Eagar");
Employee employee2 = new Employee("Tom", "Smith");
Employee employee3 = new Employee("Bill", "Joy");
Employee employee4 = new Employee("Bill", "Gates");
Employee employee5 = new Employee("Alice", "Wooden");
setEmployees.add(employee1);
setEmployees.add(employee2);
setEmployees.add(employee3);
setEmployees.add(employee4);
setEmployees.add(employee5);
System.out.println(setEmployees);

Output:

[Alice Wooden, Bill Gates, Bill Joy, Tom Eagar, Tom Smith]

So far we have got understanding about the natural ordering of objects and how the Comparable interface defines the ordering.

What if we want to sort objects in an order which differs from the natural ordering? For example, sort the employees list above by seniority (based on their join dates)?

 

2. Understanding Comparator

The Collections utility class provides a method for sorting a list using an external comparator:

Collections.sort(list, comparator)

This overloaded version takes two parameters: a list collection and a comparator, which is any object that implements the Comparatorinterface. This interface declares this method:

public interface Comparator<T> {
	public int compare(T obj1, T obj2);
}

Like the Comparable interface, this interface is also parameterized for any specific type. The compare() method is similar except it takes both the objects to be compared as arguments. The return value is also evaluated similarly .

For example, the following class compares two Employee objects using the Comparator interface:

/**
 * EmployeeComparator.java
 * Implementing Comparator
 * @author www.codejava.net
 */
public class EmployeeComparator implements Comparator<Employee> {
	public int compare(Employee emp1, Employee emp2) {
		return emp1.getJoinDate().compareTo(emp2.getJoinDate());
	}
}

In this comparator, we compare two Employee objects by their join dates. And update the Employee class like this (add an overloaded constructor and update the toString() method):

/**
 * Employee.java
 * Implementing Comparable
 * @author www.codejava.net
 */
public class Employee implements Comparable<Employee> {
	// fields...
	// getters & setters....
	// constructor
	
	public Employee(String firstName, String lastName, Date joinDate) {
		this.firstName = firstName;
		this.lastName = lastName;
		this.joinDate = joinDate;
	}
	public String toString() {
		DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
		return firstName + " " + lastName + " " + dateFormat.format(joinDate);
	}
}

And here’s the test code:

DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
List<Employee> listEmployees = new ArrayList<>();
Employee employee1 = new Employee("Tom", "Eagar", dateFormat.parse("2007-12-03"));
Employee employee2 = new Employee("Tom", "Smith", dateFormat.parse("2005-06-20"));
Employee employee3 = new Employee("Bill", "Joy", dateFormat.parse("2009-01-31"));
Employee employee4 = new Employee("Bill", "Gates", dateFormat.parse("2005-05-12"));
Employee employee5 = new Employee("Alice", "Wooden", dateFormat.parse("2009-01-22"));
listEmployees.add(employee1);
listEmployees.add(employee2);
listEmployees.add(employee3);
listEmployees.add(employee4);
listEmployees.add(employee5);
System.out.println("Before sorting: ");
System.out.println(listEmployees);
Collections.sort(listEmployees, new EmployeeComparator());
System.out.println("After sorting: ");
System.out.println(listEmployees);
Collections.sort(listEmployees, (emp1, emp2) -> emp1.getJoinDate().compareTo(emp2.getJoinDate()));

Output:

Before sorting:
[Tom Eagar 2007-12-03, Tom Smith 2005-06-20, Bill Joy 2009-01-31, Bill Gates 2005-05-12, Alice Wooden 2009-01-22]
After sorting:
[Bill Gates 2005-05-12, Tom Smith 2005-06-20, Tom Eagar 2007-12-03, Alice Wooden 2009-01-22, Bill Joy 2009-01-31]

 

Note #3:

Since Java 8, we can use Lambda expressions to create a comparator more easily like this:

Collections.sort(listEmployees, 
			(emp1, emp2) -> emp1.getJoinDate().compareTo(emp2.getJoinDate()));

We can also pass a comparator when creating a new instance of a TreeSet like this:

Set<Employee> setEmployees = new TreeSet<>(new EmployeeComparator());

Then the TreeSet will sort its elements according to the order defined by the specified comparator.

Using a comparator is useful in the following scenarios:

  • The class doesn’t have natural ordering (or we don’t have source code to update it).
  • We want to sort objects in orders other than the natural ordering.
  • We want to provide multiple ways for sorting the objects, e.g. one comparator for each sorting criteria.

 

3. The contract between natural ordering and equals

You know, the documentation of both Comparable and Comparator states that the natural ordering and the ordering specified by a comparator should be consistent with the equals() method of the class. Let’s say we have two objects obj1 and obj2 of class A, then:

If obj1.compareTo(obj2) = 0 then obj1.equals(obj2) = true

If this contract is violated, we will get strange behavior when using sorted collections such as TreeSet and TreeMap.

Let’s examine an example to understand why this constraint really matters. Come back to the example of sorting a list of Employee objects.

We haven’t overridden the equals() method yet. Now, let’s override it for the Employee class:

/**
 * Employee.java
 * Implementing Comparable and override equals()
 * @author www.codejava.net
 */
public class Employee implements Comparable<Employee> {
	// fields, constructors, getters and setters and toString()...
	
	public int compareTo(Employee another) {
		int compareValue = this.firstName.compareTo(another.firstName);
		if (compareValue == 0) {
			return this.lastName.compareTo(another.lastName);
		}
		return compareValue;
	}
	public boolean equals(Object obj) {
		if (obj instanceof Employee) {
			Employee another = (Employee) obj;
			if (this.firstName.equals(another.firstName)
				&& this.lastName.equals(another.lastName)) {
					return true;
			}
		}
		return false;
	}
}

Currently, it is compatible with the compareTo() method which also compares first name and then last name.

What if we need to change the compareTo() method for comparing two Employee objects by their seniority (join date) like this:

public int compareTo(Employee another) {
	return this.joinDate.compareTo(another.joinDate);
}

Let’s execute some test code to see the outcome:

DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
Set<Employee2> setEmployees = new TreeSet<>(new EmployeeComparator2());
Employee2 employee1 = new Employee2("Tom", "Eagar", dateFormat.parse("2007-12-03"));
Employee2 employee2 = new Employee2("Tom", "Smith", dateFormat.parse("2005-06-20"));
Employee2 employee3 = new Employee2("Bill", "Joy", dateFormat.parse("2007-12-03"));
Employee2 employee4 = new Employee2("Bill", "Gates", dateFormat.parse("2005-05-12"));
Employee2 employee5 = new Employee2("Alice", "Wooden", dateFormat.parse("2005-06-20"));
setEmployees.add(employee1);
setEmployees.add(employee2);
setEmployees.add(employee3);
setEmployees.add(employee4);
setEmployees.add(employee5);
System.out.println(setEmployees);

Note that the employee1and employe5 have same join date, so do the employee3 and employee4. Add all of these 5 objects to the set:

setEmployees.add(employee1);
setEmployees.add(employee2);
setEmployees.add(employee3);
setEmployees.add(employee4);
setEmployees.add(employee5);

And print the set:

System.out.println(setEmployees);

Can you guess the output? Here is it:

[Tom Smith 2005-06-20, Tom Eagar 2007-12-03, Bill Joy 2009-01-31]

Ouch! Why are there only 3 employees in the set?

It’s because the set compares the objects using the compareTo() method which considers two employees are equal if they have same join date, whereas the set does not allow duplicate elements, hence the employee4 and employee5 objects are not added to the set.

Now, you understand the consequence if natural ordering and equals are not consistent, right?

 

So is there any solution or workaround?

Yes, there is.

Suppose that we still want to keep the natural ordering based on join date, while keep compatible with the equals() method, here’s how we update the compareTo() method:

public int compareTo(Employee another) {
	int compareValue = this.joinDate.compareTo(another.joinDate);
	if (compareValue == 0) {
		compareValue = this.firstName.compareTo(another.firstName);
		if (compareValue == 0) {
			return this.lastName.compareTo(another.lastName);
		}
		return compareValue;
	}
	return compareValue;
}

That’s it! In this solution, we compare the Employee objects by their join dates first. If equal, continue comparing by their first names. And if equal, continue comparing their last names. This way we can keep the compareTo() method compatible with the equals() method.

Run the test code again and observe the output:

[Tom Smith 2005-06-20, Alice Wooden 2007-12-03, Tom Eagar 2007-12-03, Bill Gates 2009-01-31, Bill Joy 2009-01-31]

Perfect!

The same problem and solution applies for a comparator.

Finally, I recommend you to look at the documentation of the Comparable and Comparator to understand more about the contract we’ve discussed so far:

 

Other Java Collections Tutorials:


About the Author:

is certified Java programmer (SCJP and SCWCD). He began programming with Java back in the days of Java 1.4 and has been passionate about it ever since. You can connect with him on Facebook and watch his Java videos on YouTube.



Add comment

   


Comments 

#8Nam2020-12-22 00:35
Hi Praveen,
Thanks for sharing that nice info.
Quote
#7Praveen2020-12-19 05:37
I have alternative using Java8 Comparator.comparing

// TreeSet of Employee sorted based on first name followed by last name and DateOfJoining
Set employeeSet = new TreeSet(
Comparator.comparing(Employee::getFirstName)
.thenComparing(Employee::getLastName)
.thenComparing(Employee::getDoJ)
);
Quote
#6Surya2020-11-14 09:48
Thank you for this in depth tutorial and the useful illustrative examples, it helped very much in understanding the relevant concepts.
Quote
#5tofail2019-10-21 03:18
dear this is a great article. But look at the example that you have provide at the end where you print 3 employees. output is not matching according to input .. please correct it.
Quote
#4Syed Abdul Wahab2019-10-05 00:07
Thank you for another great tutorial Nam!
Quote