Sorting In Java8 With Lambdas and Comparators By Example

Lets learn how to sort objects in Java8 with the use of lambdas and comparators. Java provides two ways of sorting objects, the Comparable interface, and the Comparator class. They sound very similar so they are easy to get confused so lets also take a look at the difference between these two.

The Comparable Interface

The Comparable interface only has 1 method

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

This is an interface so it means that any object can implement Comparable. We will be able to sort any object that implements Comparable using Collections.sort();

When implementing compareTo, the values that should be returned are:

  • 0 where the current object is equal to the comparison object
  • Negative when the current object is smaller than the comparison object
  • Positive if the current object is greater than the comparison object

Lets show an example to see how this works.

package com.colwil.examples.sort;

import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.junit.Test;

class Fish implements Comparable<Fish> {
    private String name;

    public Fish(String name) {
	this.name = name;
    }
    public String toString() {
	return name;
    }
    public int compareTo(Fish f) {
	return name.compareTo(f.name);
    }
}

public class SortTest {

    @Test
    public void sortFish() {

	List<Fish> f = new ArrayList<>();
	f.add(new Fish("Shark"));
	f.add(new Fish("Cod"));
	System.out.println("Before sort: " + f);
	Collections.sort(f);
	System.out.println("After sort: " + f);
	assertThat("The list of fish will be sorted in the right order", f.get(0).toString(), is("Cod"));
    }
}

We have our fish object that implements compareTo and uses the name String to do the comparison. String already implements the compareTo interface so we can use that for comparing Strings. If we run the unit test we get the result below.

Before sort: [Shark, Cod]
After sort: [Cod, Shark]

So before we sorted the List, objects were in there in the order they were added. After the sort, the objects we sorted based on the names.

Difference Between Comparable and Comparator

Now given that we can sort using the Comparable interface why do we need Comparator. Well what do you do if theres an existing object, and you want to be able to sort that, but the object doesnt implement the Comparable interface? Or it implements the Comparable interface, but doesnt sort on the fields that you want to sort on.

The Comparator class allows you to sort by more than one method, not restricted to something already definied in a compareTo() method, and also gives you the ability to sort objects that dont implement the Comparable interface.

Sorting with Comparator

If we add a colour to our fish, we can also add a Comparator that lets us sort by colour. So first lets add the colour as below. We have added in a default value for the colour if they only pass the name of the fish.

class Fish implements Comparable<Fish> {
    String name;
    String colour;

    public Fish(String name) {
	this.name = name;
	this.colour = "Yellow";
    }

    public Fish(String name, String colour) {
	this.name = name;
	this.colour = colour;
    }

    public String toString() {
	return "Name:" + name + " Colour:" + colour;
    }

    public int compareTo(Fish f) {
	return name.compareTo(f.name);
    }
}

So all we have done here is added the colour to the fish. Notice that our implementation of Comparable in the compareTo() method has not changed.

Now lets create another unit test that sorts our fish by colour rather than by name


    @Test
    public void sortFishByColour() {

	Comparator<Fish> byColour = new Comparator<Fish>() {
	    public int compare(Fish f1, Fish f2) {
		return f1.colour.compareTo(f2.colour);
	    }
	};

	List<Fish> f = new ArrayList<>();
	f.add(new Fish("Shark", "White"));
	f.add(new Fish("Cod", "Black"));
	f.add(new Fish("Starfish", "Orange"));

	System.out.println("Before sort: " + f);
	Collections.sort(f, byColour);
	System.out.println("After sort: " + f);

	assertThat("The list of fish will be sorted in the right order", f.get(0).toString(),
		is("Name:Cod Colour:Black"));
    }

And the result of running out unit test is

Before sort: [Name:Shark Colour:White, Name:Cod Colour:Black, Name:Starfish Colour:Orange]
After sort: [Name:Cod Colour:Black, Name:Starfish Colour:Orange, Name:Shark Colour:White]

So as you can see, we are sorting by colour. This is because on the Collections.sort() method we passed the Comparator to use for the sort.

Collections.sort(f, byColour);

Defining the Comparator Using A Lambda Expression.

So we defined our Comparator using an implementation of the compare method. But Comparator is a functional interface as it only declares one abstract method. So this means we can also redefine that very easily using a lambda expression. If we do that we can replace

	Comparator<Fish> byColour = new Comparator<Fish>() {
	    public int compare(Fish f1, Fish f2) {
		return f1.colour.compareTo(f2.colour);
	    }
	};

with

Comparator<Fish> byColour = (f1, f2) -> f1.colour.compareTo(f2.colour);

so now our unit test looks like

@Test
public void sortFishByColourWithLambda() {

Comparator<Fish> byColour = (f1, f2) -> f1.colour.compareTo(f2.colour);

List<Fish> f = new ArrayList<>();
f.add(new Fish("Shark", "White"));
f.add(new Fish("Cod", "Black"));
f.add(new Fish("Starfish", "Orange"));

System.out.println("Before sort: " + f);
Collections.sort(f, byColour);
System.out.println("After sort: " + f);

assertThat("The list of fish will be sorted in the right order", f.get(0).toString(),
    is("Name:Cod Colour:Black"));
}

So the only thing we have changed is the use of the lambda expression for the Comparator. And if we run our unit test now we get the same result as before.

Before sort: [Name:Shark Colour:White, Name:Cod Colour:Black, Name:Starfish Colour:Orange]
After sort: [Name:Cod Colour:Black, Name:Starfish Colour:Orange, Name:Shark Colour:White]

References

Oracle – Lambda Expressions

Leave a Comment

Your email address will not be published. Required fields are marked *