June 27, 2016

Sorting objects - working with "Java Comparator"

Today I want to show you how to sort objects by one or more properties. There are a lot of possibilities how to do that.
There are two interfaces in Java which help you to sort a list. There is the Comparator interface and the Comparable interface. So how do you decide which one to use?

API interface definition Comparable<T>:
"This interface imposes a total ordering on the objects of each class that implements it. This ordering is referred to as the class's natural ordering, and the class's compareTo method is referred to as its natural comparison method."  [1]

Simply put, if you implement the Comparable interface in a class, the class instance is able to compare itself to another object. So you can just use Collections.sort(List<T> list); and you will get a sorted list.



API interface definition  Comparator<T>:
"A comparison function, which imposes a total ordering on some collection of objects. Comparators can be passed to a sort method (such as Collections.sort or Arrays.sort) to allow precise control over the sort order. Comparators can also be used to control the order of certain data structures (such as sorted sets or sorted maps), or to provide an ordering for collections of objects that don't have a natural ordering." [2]

Again simply put, if you want to use the Comparator interface, there are several ways to implement it

1. Create a custom Comparator class. Implement the comparator interface and use this class on Collections.sort(List<T> list, Comparator<? super T> c)
2. Use an anonymous class when you are using Collections.sort(List<T> list, Comparator<? super T> c)
3. You can also use a lamdba expression for this, which I will explain in more detail later in this post.

Rules [3]

  1. If the current object is less-than the other object you have to return a negative number.
  2. If the current object is greater than the other object you have to return a positive number.
  3. If both objects are equal you have to return zero.
Recommendation
"It is strongly recommended (though not required) that natural orderings be consistent with equals. This is so because sorted sets (and sorted maps) without explicit comparators behave "strangely" when they are used with elements (or keys) whose natural ordering is inconsistent with equals. In particular, such a sorted set (or sorted map) violates the general contract for set (or map), which is defined in terms of the equals method." [2]

It is good practice to implement the equals method of the class, because otherwise, if you are using a HashSet or a TreeSet, duplicate detection wouldn't work, because they rely on the compareTo and equals methods.

Example

To put above insights into practice, I'll illustrate the correct use of implementing a comparator or comparable in the code samples below. Generally, the use of an anonymous inner class leads to an overhead in the code, which also leads to less understandable code. If, for instance, you had a collection of customers and you wanted to sort it by name ascending, you could go about it like this:

 public class Customer{  
      private String name;  
      private int age;  
      public Customer(String name, int age){  
           this.name = name;  
           this.age = age;  
      }  
      public String getName(){  
           return this.name;  
      }  
      public int getAge(){  
           return this.age;  
      }  
 }  

 List<Customer> customers = new ArrayList<>();  
 customers.add(new Customer("Andreas", 20));  
 customers.add(new Customer("Dennis", 25));  
 customers.add(new Customer("Bernd", 19));  
 customers.add(new Customer("Thomas", 14));  

 Collections.sort(customers, new Comparator<Customer>() {  
      @Override  
      public int compare(Customer a, Customer b){  
           return a.getName().compareTo(b.getName());  
      }  
 });  

Alternatively, you could have created a custom Comparator class as illustrated below. That way, you are able to change the needed comparison implementation whenever you need to.

 public class NameComparator implements Comparator<Customer>{  
   @Override  
   public int compare(Customer o1, Customer o2) {  
     return o1.getName().compareTo(o2.getName());  
   }  
 }  
 Collections.sort(customers, new NameComparator());  

If you now iterate through the customers and print the names, you will get an alphabetical list.
In addition, you could also implement the Comparable interface in the Customer class as shown in the following code. But the major disadvantage in that case is that you cannot change the order or property you want to compare. With this implementation you have to use the comparison by name and the ascending order.

 public class Customer implements Comparable<Customer>{  
      private String name;  
      private int age;  

      public Customer(String name, int age){  
           this.name = name;  
           this.age = age;  
      }  

      public String getName(){  
           return this.name;  
      }  

      public int getAge(){  
           return this.age;  
      }  

      @Override  
      public int compareTo(Customer o) {  
         return this.getName().compareTo(o.getName());  
      }  

      @Override  
      public String toString() {  
        return "Customer{" +  
         "name='" + name + '\'' +  
         ", age=" + age +  
         '}';  
      }  
 }  

Java 8


In Java 8 you can use lamdba expressions instead of anonymous inner classes. This reduces a lot of "code smell":

 Collections.sort(customers, (c1, c2) -> c1.getName().compareTo(c2.getName()))  

Another useful feature that was added in Java 8 is that a List now has a sort method:

 List<T>.sort(Comparator<? super E> c)  

And you can use it like this:

 customers.sort((c1, c2) -> c1.getName().compareTo(c2.getName());  

So which interface should I use?

If you want to have control over how to compare a class, I would suggest that you use the Comparator interface, because you can define multiple custom Comparators and you can use them whenever needed.

If you really just want to use one way of comparing a class or if the object is not for you to control, you should use the Compareable interface.

The new lambda feature of Java 8 will help you write more elegant and easy-to-understand code. So if you have the chance to upgrade to Java 8, you should use it.

[1]: Comparable API: http://docs.oracle.com/javase/6/docs/api/java/lang/Comparable.html
[2]: Comparator API: http://docs.oracle.com/javase/6/docs/api/java/util/Comparator.html
[3]: How to override compareTo method: http://javarevisited.blogspot.com/2011/11/how-to-override-compareto-method-in.html

1 comment:

  1. This comment has been removed by the author.

    ReplyDelete