Sorting a List using Comparable<T> or a Comparator<T>


Say we are working on a class representing a Person by their first and last names. We have created a basic class to do this and implemented proper equals and hashCode methods.

public class Person {
 
	private final String lastName; //invariant - nonnull
	private final String firstName; //invariant - nonnull
 
	public Person(String firstName, String lastName){
		this.firstName = firstName != null ? firstName : "";
		this.lastName = lastName != null ? lastName : "";
	}
 
	public String getFirstName() {
		return firstName;
	}
 
	public String getLastName() {
		return lastName;
	}
 
	public String toString() {
		return lastName + ", " + firstName;
	}
 
	@Override
	public boolean equals(Object o) {
		if (! (o instanceof Person)) return false;
		Person p = (Person)o;
		return firstName.equals(p.firstName) && lastName.equals(p.lastName);
	}
 
	@Override
	public int hashCode() {
		return Objects.hash(firstName, lastName);
	}
}

Now we would like to sort a list of Person objects by their name, such as in the following scenario:

public static void main(String[] args) {
	List<Person> people = Arrays.asList(
				new Person("John", "Doe"),
				new Person("Bob", "Dole"),
				new Person("Ronald", "McDonald"),
				new Person("Alice", "McDonald"),
				new Person("Jill", "Doe")
				);
	Collections.sort(people); //This currently won't work.
}

Unfortunately, as marked, the above currently won't compile. Collections.sort(..) only knows how to sort a list if the elements in that list are comparable, or a custom method of comparison is given.

If you were asked to sort the following list : 1,3,5,4,2, you'd have no problem saying the answer is 1,2,3,4,5. This is because Integers (both in Java and mathematically) have a natural ordering, a standard, default comparison base ordering. To give our Person class a natural ordering, we implement Comparable, which requires implementing the method compareTo(Person p):

public class Person implements Comparable<Person> {
 
	private final String lastName; //invariant - nonnull
	private final String firstName; //invariant - nonnull
 
	public Person(String firstName, String lastName) {
		this.firstName = firstName != null ? firstName : "";
		this.lastName = lastName != null ? lastName : "";
	}
 
	public String getFirstName() {
		return firstName;
	}
 
	public String getLastName() {
		return lastName;
	}
 
	public String toString() {
		return lastName + ", " + firstName;
	}
 
	@Override
	public boolean equals(Object o) {
		if (! (o instanceof Person)) return false;
		Person p = (Person)o;
		return firstName.equals(p.firstName) && lastName.equals(p.lastName);
	}
 
	@Override
	public int hashCode() {
		return Objects.hash(firstName, lastName);
	}
 
	@Override
	public int compareTo(Person other) {
		// If this' lastName and other's lastName are not comparably equivalent,
		// Compare this to other by comparing their last names.
		// Otherwise, compare this to other by comparing their first names
		int lastNameCompare = lastName.compareTo(other.lastName);
		if (lastNameCompare != 0) {
			return lastNameCompare;
		} else {
			return firstName.compareTo(other.firstName);
		}
	}
}

Now, the main method given will function correctly

public static void main(String[] args) {
	List<Person> people = Arrays.asList(
					new Person("John", "Doe"),
					new Person("Bob", "Dole"),
					new Person("Ronald", "McDonald"),
					new Person("Alice", "McDonald"),
					new Person("Jill", "Doe")
					);
	Collections.sort(people); //Now functions correctly
	//people is now sorted by last name, then first name:
	// --> Jill Doe, John Doe, Bob Dole, Alice McDonald, Ronald McDonald
}

If, however, you either do not want or are unable to modify class Person, you can provide a custom Comparator<T> that handles the comparison of any two Person objects. If you were asked to sort the following list: circle, square, rectangle, triangle, hexagon you could not, but if you were asked to sort that list based on the number of corners, you could. Just so, providing a comparator instructs Java how to compare two normally not comparable objects.

public class PersonComparator implements Comparator<Person> {
	public int compare(Person p1, Person p2) {
		// If p1's lastName and p2's lastName are not comparably equivalent,
		// Compare p1 to p2 by comparing their last names.
		// Otherwise, compare p1 to p2 by comparing their first names
		if (p1.getLastName().compareTo(p2.getLastName()) != 0) {
			return p1.getLastName().compareTo(p2.getLastName());
		} else {
			return p1.getFirstName().compareTo(p2.getFirstName());
		}
	}
}
 
//Assume the first version of Person (that does not implement Comparable) is used here
public static void main(String[] args) {
	List<Person> people = Arrays.asList(
						new Person("John", "Doe"),
						new Person("Bob", "Dole"),
						new Person("Ronald", "McDonald"),
						new Person("Alice", "McDonald"),
						new Person("Jill", "Doe")
						);
	Collections.sort(people); //Illegal, Person doesn't implement Comparable.
	Collections.sort(people, new PersonComparator()); //Legal
	//people is now sorted by last name, then first name:
	// --> Jill Doe, John Doe, Bob Dole, Alice McDonald, Ronald McDonald
}

Comparators can also be created/used as an anonymous inner class

//Assume the first version of Person (that does not implement Comparable) is used here
public static void main(String[] args) {
	List<Person> people = Arrays.asList(
					new Person("John", "Doe"),
					new Person("Bob", "Dole"),
					new Person("Ronald", "McDonald"),
					new Person("Alice", "McDonald"),
					new Person("Jill", "Doe")
					);
	Collections.sort(people); //Illegal, Person doesn't implement Comparable.
 
	Collections.sort(people, new PersonComparator()); //Legal
 
	//people is now sorted by last name, then first name:
	// --> Jill Doe, John Doe, Bob Dole, Alice McDonald, Ronald McDonald
 
	//Anonymous Class
	Collections.sort(people, new Comparator<Person>() { //Legal
		public int compare(Person p1, Person p2) {
			//Method code...
		}
	});
}
 
Version ≥ Java SE 8

Lambda expression based comparators

As of Java 8, comparators can also be expressed as lambda expressions

//Lambda
Collections.sort(people, (p1, p2) -> { //Legal
	//Method code....
});

Comparator default methods

Furthermore, there are interesting default methods on the Comparator interface for building comparators : the following builds a comparator comparing by lastName and then firstName.

Collections.sort(people, Comparator.comparing(Person::getLastName).thenComparing(Person::getFirstName));

Inversing the order of a comparator

Any comparator can also easily be reversed using the reversedMethod which will change ascending order to descending.

Basic Programs