Introduction
Contents
In our previous two tutorials Java Comparable & Java Comparator, we examined the details of java.lang.Comparable and java.util.Comparator. After reading and understanding both, one question still remained, Which one should be used because both seem to do the same thing? The only difference is the signatures of their respective methods. So, in this post we try to figure out the answer to this deceptively simple question, Comparable vs Comparator.
Comparison
Let’s compare the two interfaces side by side and see if one is better than the other and which to use when.
java.lang.Comparable | java.util.Comparator |
---|---|
int objOne.compareTo(objTwo) | int compare(objOne, objTwo) |
Returns negative if objOne < objTwo zero if objOne == objTwo positive if objOne > objTwo | Same as Comparable |
You must modify the class whose instances you want to sort. | You build a class separate from the class whose instances you want to sort. |
Only one sort sequence can be created. | Many sort sequences can be created. |
Implemented frequently in the API by: String, Wrapper classes, Date, Calendar. | Meant to be implemented to sort instances of third-party classes. |
int objOne.compareTo(objTwo) vs int compare(objOne, objTwo)
It just seems like a method signature difference, but there is a subtle difference.
A comparable object is capable of comparing itself with another object. The class itself must implement the java.lang.Comparable interface in order to be able to compare its instances. Whereas, A comparator object is capable of comparing two different objects. The class is not comparing its instances, but some other class’s instances. This comparator class must implement the java.util.Comparator interface.
The second comparison is not a difference, it basically specifies the return values of the methods and they are same for both.
You must “modify” the class whose instances you want to sort vs You build a class separate from the class whose instances you want to sort.
This is a very important difference. Read it again!!
To understand this better let’s take the example of our Student class. In case of comparable, your Student class implements Comparable interface and overrides compareTo method. Let’s say at one point, your teammate needs to sort students based on their rollNumbers. So his student class would look something like:
1 2 3 4 5 6 7 8 9 10 11 12 |
class Student implements Comparable<Student> { private String name; private int rollNumber; private int marksObtained; @Override public int compareTo(Student otherStudent) { return this.rollNumber - otherStudent.rollNumber; } . . . |
But if at some later point of time you need your students to be sortable based on their marks, the above Class won’t work. You will have to modify the compareTo implementation for marksObtained by the student. Something like:
1 2 3 4 |
@Override public int compareTo(Student otherStudent) { return this.marksObtained - otherStudent.marksObtained; } |
So, the problem here is you need to modify your class every time the sorting requirement changes which is not a good idea. As the Open/Close design principle goes “software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification”
If you violate this principle you might end up writing the test cases for the class which was already tested and worse, you might end up breaking someone else’s code who depended on Student’s rollNumber for sorting (your team mate). Right?
So, now the Comparator comes into picture, if instead of using Comparable you had used Comparator, you wouldn’t have to violate any design principle. Your would write his Comparators like:
1 2 3 4 5 6 7 |
public class StudentRollNumberComparator implements Comparator<Student> { @Override public int compare(Student st1, Student st2) { return st1.getRollNumber() - st2.getRollNumber(); } } |
And you can write:
1 2 3 4 5 6 7 |
public class StudentMarksComparator implements Comparator<Student> { @Override public int compare(Student st1, Student st2) { return st1.getMarksObtained() - st2.getMarksObtained(); } } |
Now, you don’t need to change the existing code and break someone else’s code. Any Student aor subtype can now use these comparators to do the comparisons.
Only one sort sequence can be created vs Many sort sequences can be created.
As we saw above every time the requirement changed, we needed to modify our Comparable Student because with Comparable we have only one sort sequence but with Comparator we created two different sort sequences. You can create even a third one which will let you sort based on Student names.
Implemented frequently in the API by: String, Wrapper classes, Date, Calendar vs Meant to be implemented to sort instances of third-party classes.
The final comparison is just about the application of the two interfaces. Where they are used at present and what are the areas of their usage.
Verdict
So, after examining all the difference carefully, have you been able to decide which interface is better. Which should be given preference?
It seems Comparator is the way to go always but this is not quite right because it is not about using one or the other. You should think about leveraging both as it will make your code more flexible and more robust at the same time.
So, if sorting of objects needs to be based on a natural order then use Comparable whereas if sorting needs to be done on different attributes of the object, use Comparator in Java.
For example, telephone directories are always sorted by the Last Name of the contacts. Which is quite natural and easier for the users, so, Comparable seems like a good choice here. But what if, some one wants to sort contacts by the city, the contacts live in, so that they could post, let’s say, a common message to all the residents of that city. Now, in this situation sorting contacts by city name seems quite reasonable but this doesn’t mean that you go ahead and start changing your existing class. Instead, you can write a CityComparator, a brand new sorting sequence which will not break the existing code and can be reused in future if similar requirement arises.
Let’s see how to use Comparable & Comparator together using the AddressBook example.
Comparable & Comparator together
To begin with, we need a Contact class whose instances will be in the Address Book.
We will be using just two fields, name and city to keep the examples simple. Natural order of sorting for a Contact is by name.
Contact.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
public class Contact implements Comparable<Contact> { // Instead of FirstName & LastName, let's use name for simplicity. private String name; private String city; public Contact(String name, String city) { this.name = name; this.city = city; } @Override public int compareTo(Contact contact) { int thisLength = this.name.length(); int thatLength = contact.name.length(); int minimumLength = Math.min(thisLength, thatLength); char thisNameChars[] = this.name.toCharArray(); char thatNameChars[] = contact.name.toCharArray(); int charIndex = 0; while (charIndex < minimumLength) { char thisChar = thisNameChars[charIndex]; char thatChar = thatNameChars[charIndex]; if (thisChar != thatChar) { return thisChar - thatChar; } charIndex++; } return thisLength - thatLength; } public String getName() { return name; } public String getCity() { return city; } @Override public String toString() { return "{ name=" + name + ", city=" + city + " }"; } } |
Next, we need an address-book, which will be read by the user using different sort orders.
AddressBook.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
import java.util.Arrays; import java.util.Set; import java.util.TreeSet; public class AddressBook { private Set<Contact> contacts; public void populateAddressBook() { // Create some Contacts Contact andy = new Contact("andy", "mumbai"); Contact bunny = new Contact("bunny", "amsterdam"); Contact clair = new Contact("clair", "london"); Contact danny = new Contact("danny", "chicago"); Contact alex = new Contact("alex", "mexico"); Contact petra = new Contact("petra", "utrecht"); Contact mike = new Contact("mike", "delhi"); Contact andrew = new Contact("andrew", "mesopotamia"); // Add Contacts to the address book contacts = new TreeSet<>(Arrays.asList( andy, bunny, clair, danny, alex, petra, mike, andrew)); } public Set<Contact> getContacts() { return contacts; } } |
So far the users of address-book can view the contacts sorted by their names because as we mentioned earlier, contacts have a natural order by-name.
Now comes the second part of our example. What if, user wants to see the contacts sorted by their city instead of their name. Let’s write a comparator which allows us to do just that.
CityComparator.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
import java.util.Comparator; public class CityComparator implements Comparator<Contact> { /* * We are using the same implementation as we used in out compareaTo method on * Contact.java. * The only difference is that here we have contact1 instead of 'this' and * we are using 'city' field instead of the 'name' field */ @Override public int compare(Contact contact1, Contact contact2) { int contact1Length = contact1.getCity().length(); int thatLength = contact2.getCity().length(); int minimumLength = Math.min(contact1Length, thatLength); char contact1CityChars[] = contact1.getCity().toCharArray(); char contact2CityChars[] = contact2.getCity().toCharArray(); int charIndex = 0; while (charIndex < minimumLength) { char contact1Char = contact1CityChars[charIndex]; char contact2Char = contact2CityChars[charIndex]; if (contact1Char != contact2Char) { return contact1Char - contact2Char; } charIndex++; } return contact1Length - thatLength; } } |
Now, let’s test run our code:
AddressBookDemo.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
import java.util.Set; import java.util.TreeSet; public class AddressBookDemo { public static void main(String[] args) { // To start the demo, first of all we need an Address book AddressBook addressBook = new AddressBook(); // We need some addresses in the AddressBook for it to be useful addressBook.populateAddressBook(); // Let's see our contacts Set<Contact> contacts = addressBook.getContacts(); System.out.println(contacts); // Now we need to see our contacts sorted by city, which is a change in requirement // So, let's just use our city comparator and sort the address Set<Contact> contactsSortedByCity = new TreeSet<>(new CityComparator()); contactsSortedByCity.addAll(contacts); System.out.println(contactsSortedByCity); } } // ============ Output ================ // Sorted by Name // [{ name=alex, city=mexico }, { name=andrew, city=mesopotamia }, { name=andy, city=mumbai }, { name=bunny, city=amsterdam }, { name=clair, city=london }, { name=danny, city=chicago }, { name=mike, city=delhi }, { name=petra, city=utrecht }] // Sorted by City // [{ name=bunny, city=amsterdam }, { name=danny, city=chicago }, { name=mike, city=delhi }, { name=clair, city=london }, { name=andrew, city=mesopotamia }, { name=alex, city=mexico }, { name=andy, city=mumbai }, { name=petra, city=utrecht }] |
I hope, now we are clear on how to use Comparable and Comparator and how to answer the important interview question on Comparable vs Comparator like
- Which to choose when?
- Which is better or preferred over the other?
Related Posts
If you have any query or feedback, please let me know. Take care!!