compareTo, compare Methods and Natural vs explicit sorting


The compareTo and compare Methods

The Comparable<T> interface requires one method:
 
  public interface Comparable<T> {
 
	public int compareTo(T other);
  }
 
And the Comparator<T> interface requires one method:
 
  public interface Comparator<T> {
 
	public int compare(T t1, T t2);
  }

These two methods do essentially the same thing, with one minor difference: compareTo compares this to other, whereas compare compares t1 to t2, not caring at all about this.

Aside from that difference, the two methods have similar requirements. Specifically (for compareTo), Compares this object with the specified object for order. Returns a negative integer, zero, or a positive integer as this object is less than, equal to, or greater than the specified object. Thus, for the comparison of a and b:

  • If a < b, a.compareTo(b) and compare(a,b) should return a negative integer, and b.compareTo(a) and compare(b,a) should return a positive integer
  • If a > b, a.compareTo(b) and compare(a,b) should return a positive integer, and b.compareTo(a) and compare(b,a) should return a negative integer
  • If a equals b for comparison, all comparisons should return 0.

Natural (comparable) vs explicit (comparator) sorting

There are two Collections.sort() methods:

  • One that takes a List<T> as a parameter where T must implement Comparable and override the compareTo() method that determines sort order.
  • One that takes a List and a Comparator as the arguments, where the Comparator determines the sort order.

First, here is a Person class that implements Comparable:

public class Person implements Comparable<Person> { 
	private String name; 
	private int age;
 
	public String getName() {
		return name;
	}
 
	public void setName(String name) {
		this.name = name;
	}
 
	public int getAge() {
		return age;
	}
 
	public void setAge(int age) {
		this.age = age;
	} 
 
	@Override
	public int compareTo(Person o) {
		return this.getAge() - o.getAge();
	}
 
	@Override
	public String toString() {
		return this.getAge()+"-"+this.getName();
	}
}

Here is how you would use the above class to sort a List in the natural ordering of its elements, defined by the compareTo() method override:

//-- usage
List<Person> pList = new ArrayList<Person>();
Person p = new Person();
p.setName("A");
p.setAge(10);
pList.add(p);
p = new Person();
p.setName("Z");
p.setAge(20);
pList.add(p);
p = new Person();
p.setName("D");
p.setAge(30);
pList.add(p);
 
//-- natural sorting i.e comes with object implementation, by age
Collections.sort(pList);
System.out.println(pList);

Here is how you would use an anonymous inline Comparator to sort a List that does not implement Comparable, or in this case, to sort a List in an order other than the natural ordering:

//-- explicit sorting, define sort on another property here goes with name
Collections.sort(pList, new Comparator<Person>() {
	@Override
	public int compare(Person o1, Person o2) {
		return o1.getName().compareTo(o2.getName());
	}
}); 
System.out.println(pList);

Creating a Comparator using comparing method

Comparator.comparing(Person::getName)

This creates a comparator for the class Person that uses this person name as the comparison source. Also it is possible to use method version to compare long, int and double. For example:

Comparator.comparingInt(Person::getAge)

Reversed order

To create a comparator that imposes the reverse ordering use reversed() method:

Comparator.comparing(Person::getName).reversed()

Chain of comparators

This will create a comparator that firs compares with last name then compares with first name. You can chain as many comparators as you want.

Comparator.comparing(Person::getLastName).thenComparing(Person::getFirstName)

Sorting Map entries

As of Java 8, there are default methods on the Map.Entry interface to allow sorting of map iterations.

Version ≥ Java SE 8
Map<String, Integer> numberOfEmployees = new HashMap<>();
numberOfEmployees.put("executives", 10);
numberOfEmployees.put("human ressources", 32);
numberOfEmployees.put("accounting", 12);
numberOfEmployees.put("IT", 100);
 
// Output the smallest departement in terms of number of employees
numberOfEmployees.entrySet().stream()
	.sorted(Map.Entry.comparingByValue())
	.limit(1)
	.forEach(System.out::println); // outputs : executives=10

Of course, these can also be used outside of the stream api :

//Version ≥ Java SE 8
List<Map.Entry<String, Integer>> entries = new ArrayList<>(numberOfEmployees.entrySet());
Collections.sort(entries, Map.Entry.comparingByValue())

Basic Programs