Sorting CollectionsJ8 Home « Sorting Collections

In our final look at collection we look at sorting our collections using the Comparable and Comparator interfaces. We have already seen examples of sorted collections when we looked at the TreeSet and TreeMap classes earlier in the section. We used some simple code examples to show how these collection types worked and were sorted in natural order according to their types. Maybe what wasn't apparent at the time was that their was no problem with ordering these collections as we were using String and Integer objects implement the Comparable interface and its only method compareTo(). We also used the Collections.sort() method in the last lesson to sort a list, this also used a String object.

But what if we create a TreeSet or TreeMap collection or try to use the Collections.sort() method to sort a custom class? To answer this question we will use the Contractor class we created in the Overriding equals() section of the The Object Superclass lesson. The Contractor class was tested to ensure that the overrides of equals() and hashCode() worked correctly. When creating your own collections you will always want to override these methods if you want meaningful, efficient collections. Following is the code for the Contractor class enhanced to use some getter methods we will use later in the lesson:


package info.java8;
/*
 A Contractor class
*/ 
public class Contractor {
    private String name = "";      // The Contractor Name.
    private String location = "";  // City Location.
    private String owner = "";     // The Customer Id.
    
    Contractor() {
    } 
    Contractor(String name, String location, String owner) {
        this.name = name;
        this.location = location;
        this.owner = owner;
    } 
    public boolean equals(Object obj) {
        if (this==obj) { // Same reference variable so equal
            return true;
        }
        if (!(obj instanceof Contractor)) // Make sure we have a Contractor instance
            return false;
        Contractor other = (Contractor) obj; // We need to cast to Contractor for comparison
        if (location == null) { // Compare locations
            if (other.location != null)
                return false;
        } else if (!location.equals(other.location))
            return false;
        if (name == null) { // Compare names
            if (other.name != null)
                return false;
        } else if (!name.equals(other.name))
            return false;
        return true;
    }
    public int hashCode() {
        String hashKey = name + location;
        return hashKey.hashCode();
    } 
    // Getter methods
    public String getName() {
        return name;
    }
    public String getLocation() {
        return location;
    }
    public String getOwner() {
        return owner;
    }
}

Save and compile the Contractor class in directory   c:\_Collections in the usual way.

Now lets create a TreeSet and store some Contractor reference variables within it.


package info.java8;
/*
  Simple class to sort some Contractor records with a TreeSet
*/
import java.util.*; // Import the java.util package

public class ContractorSort {

    public static void main (String[] args) {
        Contractor a = new Contractor();
        Contractor b = new Contractor();
        Contractor c = new Contractor("Contractor A", "Essex", "1234");
        Contractor d = new Contractor("Contractor B", "Essex", "1234");
        Contractor e = new Contractor("Contractor A", "Essex", "5678");
        Set<Contractor> ts = new TreeSet<Contractor>();
        ts.add(a);
        ts.add(b);
        ts.add(c);
        ts.add(d);
        ts.add(e);
        int count = 0;
        // Use enhanced for loop to iterate over the collection
        for (Contractor con : ts) { 
            System.out.println("record: " + ++count + " " + con.getName() + con.getLocation() 
                                          + con.getOwner());  // Print elements
        }
    }
}

Save, compile and run the ContractorSort class in directory   c:\_Collections in the usual way.

run Contractor sort

As the screenshot shows the code compiles fine but when we try to run it we get a ClassCastException. The problem is that the TreeSet class has no idea how our collection of Contractor objects should be sorted. So when the second Contractor is added we get the runtime error.

Ok, it seems we can't use an ordered, sorted collection for our Contractor objects. What about something like an ArrayList that we can sort using the Collections.sort() method? Lets try it:


package info.java8;
/*
  Simple class to sort some Contractor records using java.util.Collections.sort()
*/
import java.util.*; // Import the java.util package

public class ContractorSort2 {

    public static void main (String[] args) {
        Contractor a = new Contractor();
        Contractor b = new Contractor();
        Contractor c = new Contractor("Contractor A", "Essex", "1234");
        Contractor d = new Contractor("Contractor B", "Essex", "1234");
        Contractor e = new Contractor("Contractor A", "Essex", "5678");
        List<Contractor> al = new ArrayList<Contractor>();
        al.add(a);
        al.add(b);
        al.add(c);
        al.add(d);
        al.add(e);
        Collections.sort(al);
        int count = 0;
        // Use enhanced for loop to iterate over the collection
        for (Contractor con : al) { 
            System.out.println("index: " + count++ + " " + con.getName() + con.getLocation() 
                                         + con.getOwner());  // Print elements
        }
    }
}

Save and compile the ContractorSort2 class in directory   c:\_Collections in the usual way.

run Contractor sort

As the screenshot shows the code doesn't even compile. There are two sort() methods in the java.util.Collections class which expect the objects being sorted to either implement the Comparable or Comparator interfaces, which our Contractor class does not. As these scenarios show, the only way to sort our bespoke objects is to implement one of these interfaces.

java.lang.Comparable<T> Interface Top

The first way to allow our Contractor class to be sorted is by using the java.lang.Comparable interface. We need to implement the Comparable interface and its only method compareTo() within our Contractor class. The compareTo() method returns a negative integer, zero, or a positive integer dependant upon this object being less than, equal to, or greater than the specified object. The following code shows our updated Contractor class, retrofitted to use the Comparable interface:


package info.java8;
/*
 A Contractor class
*/ 
public class Contractor implements Comparable<Contractor> { // Implement Comparable
    private String name = "";
    private String location = "";
    private String owner = "";
    
    Contractor() {
    } 
    Contractor(String name, String location, String owner) {
        this.name = name;
        this.location = location;
        this.owner = owner;
    } 
    public boolean equals(Object obj) {
        if (this==obj) {
            return true;
        }
        if (!(obj instanceof Contractor))
            return false;
        Contractor other = (Contractor) obj;
        if (location == null) {
            if (other.location != null)
                return false;
        } else if (!location.equals(other.location))
            return false;
        if (name == null) {
            if (other.name != null)
                return false;
        } else if (!name.equals(other.name))
            return false;
        return true;
    }
    public int hashCode() {
        String hashKey = name + location;
        return hashKey.hashCode();
    } 
    // Getter methods
    public String getName() {
        return name;
    }
    public String getLocation() {
        return location;
    }
    public String getOwner() {
        return owner;
    } 
    // Implement compareTo() method of Comparable interface
    public int compareTo(Contractor c) {
        String str1 = name + location;
        String str2 = c.name + c.location;
        return str1.compareTo(str2);
    }
}

Save and compile the Contractor class in directory   c:\_Collections in the usual way.

Rerun the ContractorSort class (which used a TreeSet) in directory   c:\_Collections in the usual way.

run Contractor sort b

As the screenshot shows the ContractorSort class now works and sorts our collection by name and location. Remember that Sets do not allow duplicates so there are only 3 records shown.

Save, compile and run the ContractorSort2 class (which used the java.util.Collection.sort() method) in directory   c:\_Collections in the usual way.

run Contractor sort 2b

As the screenshot shows the ContractorSort2 class now works and sorts our collection by name and location.

java.util.Comparator<T> Interface Top

The second way to allow our Contractor class to be sorted is by using the java.util.Comparator interface. The Comparable interface works fine but you have to change the class you want sorted and is a 'one-trick pony' because you can only sort the collection in one way. But what about sorting classes we can't modify or we want to sort in different ways. Luckily for us Java also has the java.util.Comparator<T> interface, which allows us to sort a class without changing the code within that class, which also means we can have multiple sort sequences as well. All we need to do is create a class that implements the java.util.Comparator<T> interface and its compare() method. So before we look into this lets recompile our Contractor class as it was before we used the Comparable interface:


package info.java8;
/*
 A Contractor class
*/ 
public class Contractor {
    private String name = "";      // The Contractor Name.
    private String location = "";  // City Location.
    private String owner = "";     // The Customer Id.
    
    Contractor() {
    } 
    Contractor(String name, String location, String owner) {
        this.name = name;
        this.location = location;
        this.owner = owner;
    } 
    public boolean equals(Object obj) {
        if (this==obj) { // Same reference variable so equal
            return true;
        }
        if (!(obj instanceof Contractor)) // Make sure we have a Contractor instance
            return false;
        Contractor other = (Contractor) obj; // We need to cast to Contractor for comparison
        if (location == null) { // Compare locations
            if (other.location != null)
                return false;
        } else if (!location.equals(other.location))
            return false;
        if (name == null) { // Compare names
            if (other.name != null)
                return false;
        } else if (!name.equals(other.name))
            return false;
        return true;
    }
    public int hashCode() {
        String hashKey = name + location;
        return hashKey.hashCode();
    } 
    // Getter methods
    public String getName() {
        return name;
    }
    public String getLocation() {
        return location;
    }
    public String getOwner() {
        return owner;
    }
}

Save and compile the Contractor class in directory   c:\_Collections in the usual way.

Now lets create some classes to implement the java.util.Comparator<T> interface. The compare() method of the java.util.Comparator<T> interface returns an integer in the same way as the compareTo() method of the java.lang.Comparable<T> interface. We can take advantage of this by letting the compareTo() method do the comparisons:


package info.java8;
/*
  Simple class to implement Comparator to sort by Contractor.name 
*/
import java.util.*;

public class ComparatorSortName implements Comparator<Contractor>{

    public int compare(Contractor a, Contractor b) {
        return a.getName().compareTo(b.getName());
    }
}

/*
  Simple class to implement Comparator to sort by Contractor.location
*/
import java.util.*;

public class ComparatorSortLocation implements Comparator<Contractor>{

    public int compare(Contractor a, Contractor b) {
        return a.getLocation().compareTo(b.getLocation());
    }
}

/*
  Simple class to implement Comparator to sort by Contractor.owner
*/
import java.util.*;

public class ComparatorSortOwner implements Comparator<Contractor>{

    public int compare(Contractor a, Contractor b) {
        return a.getOwner().compareTo(b.getOwner());
    }
}

Save and compile the ComparatorSortName, ComparatorSortLocation and ComparatorSortOwner classes in directory   c:\_Collections in the usual way.

run Compile Comparators

Lets create a new class to sort our Contractor objects using our comparator classes ArrayList that we can sort using the Collections.sort() method that takes a comparator:


package info.java8;
/*
  Simple class to sort some Contractor records using java.util.Collections.sort() with comparator
*/
import java.util.*; // Import the java.util package

public class ContractorSort3 {

    public static void main (String[] args) {
        Contractor a = new Contractor("Contractor C", "Essex", "7752");
        Contractor b = new Contractor("Contractor F", "Surrey", "1234");
        Contractor c = new Contractor("Contractor A", "Surrey", "1111");
        Contractor d = new Contractor("Contractor B", "Kent", "6999");
        Contractor e = new Contractor("Contractor A", "Kent", "5678");
        List<Contractor> al = new ArrayList<Contractor>();
        al.add(a);
        al.add(b);
        al.add(c);
        al.add(d);
        al.add(e);
        ComparatorSortName csn = new ComparatorSortName();
        Collections.sort(al, csn);
        int count = 0;
        // Use enhanced for loop to iterate over the collection
        for (Contractor con : al) { 
            System.out.println("index: " + count++ + " " + con.getName() + con.getLocation() 
                                         + con.getOwner());  // Print elements
        }
        ComparatorSortLocation csl = new ComparatorSortLocation();
        Collections.sort(al, csl);
        count = 0;
        // Use enhanced for loop to iterate over the collection
        for (Contractor con : al) { 
            System.out.println("index: " + count++ + " " + con.getName() + con.getLocation() 
                                         + con.getOwner());  // Print elements
        }
        ComparatorSortOwner cso = new ComparatorSortOwner();
        Collections.sort(al, cso);
        count = 0;
        // Use enhanced for loop to iterate over the collection
        for (Contractor con : al) { 
            System.out.println("index: " + count++ + " " + con.getName() + con.getLocation() 
                                         + con.getOwner());  // Print elements
        }
    }
}

Save and compile the ContractorSort3 class in directory   c:\_Collections in the usual way.

run Contractor sort

As the screenshot shows we have used our three comparator classes to sort the collection in name, location and owner order and printed out the results.

Lesson 8 Complete

In this lesson we looked at sorting our collections using the Comparable and Comparator interfaces.

What's Next?

We start a new section on Streams with an overview of the Streams API.