Friday, 12 December 2014

Difference between Comparator and Comparable in Java

Background

How do we sort numbers in Java. Following is the simple program to do that - 

    public static void main(String args[]) {
        int[] myArray = new int[]{5,10,2,1,6};
        Arrays.sort(myArray);
        for(int no : myArray) {
            System.out.print(no + " ");
        }
    }

And we get the output as : 1 2 5 6 10 

Same goes for Collections as well.

But that is for simple numbers. How do we sort complex custom Objects. For example lets say you have Student Objects and you need to sort them on the basis of their age or some other scenario may require them to be sorted alphabetically by name. In this post we will see exactly how to do this.

Ordering Custom Objects


Lets create our Student class first.

Student.java


public class Student {
    
    private String name;
    private int age;
    
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    
    @Override
    public String toString() {
        return "Student [name=" + name + ", age=" + age + "]";
    }
}



For simplicity I have just kept two parameters - name and age. Before we proceed to create group of students and order them we need to answer a question -

What is the natural ordering ? The answer will vary depending on the scenario for which you are writing your code. In this scenario we will take age as natural ordering criteria.

Why did we answer above question ? Because it is the basis of the title of this post - Difference between Comparator and Comparable in Java.

Difference between Comparator and Comparable in Java

Implementing Comparable means "I can compare myself with another object." This is typically useful when there's a single natural default comparison.

Implementing Comparator means "I can compare two other objects." This is typically useful when there are multiple ways of comparing two instances of a type - e.g. you could compare people by age, name etc.

In our case we decided age will be our natural ordering. So we will handle age comparison in Comparable interface where as handle name in Comparator.

Note : Both Comparator and Comparable are interfaces. You need to implement them. Your model class which needs to be naturally ordered implements Comparable interface where as you need to create a separate class that implements Comparator to handle non natural ordering. When you call methods like sort we need to supply instance of this class that implemented Comparator interface.

 Comparable interface is in package java.lang whereas Comparator interface is in java.util package. Also when you implement Comparable interface you override a.compareTo(b) method where as in case of Comparator you override compare(a, b). Don't worry about the technicality. We will see this with example.

Implementing Comparable interface - natural ordering

As I mentioned earlier with reference to our Student object I am going to use age as natural ordering criteria.

Lets rewrite our Student class which will now implement Comparable interface.


public class Student implements Comparable<Student> {
    
    private String name;
    private int age;
    
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    
    @Override
    public String toString() {
        return "Student [name=" + name + ", age=" + age + "]";
    }
    
    @Override
    public int compareTo(Student o) {
        return (this.age > o.age ? 1 : (this.age < o.age ? -1 : 0));
    }
}


Notice the logic in compareTo() method. We return 1 if current object is higher in order, -1 if lower and 0 if same. Lets test this now - 

public class OrderingTester {

    

    public static void main(String args[]) {

        Student[] allStudents = new Student[] { new Student("John", 21),

                new Student("Rita", 19), new Student("Sam", 26),

                new Student("Claire", 16) };


        System.out.println("Before natural sorting");

        for(Student student : allStudents) {

            System.out.println(student);

        }

        Arrays.sort(allStudents);

        System.out.println("After natural Sorting");

        for(Student student : allStudents) {

            System.out.println(student);

        }

    }

}

And the output is - 

Before natural sorting
Student [name=John, age=21]
Student [name=Rita, age=19]
Student [name=Sam, age=26]
Student [name=Claire, age=16]
After natural Sorting
Student [name=Claire, age=16]
Student [name=Rita, age=19]
Student [name=John, age=21]
Student [name=Sam, age=26]


That's how natural ordering/comparable interface work. Now lets move on to comparator interface.

Implementing Comparator interface - Custom ordering

Lets start by writing our comparator class - 

public class StudentNameComparator implements Comparator<Student> {

    @Override
    public int compare(Student student1, Student student2) {
        return student1.getName().compareTo(student2.getName());
    }

}

and now lets test this - 

public class OrderingTester {
    
    public static void main(String args[]) {
        Student[] allStudents = new Student[] { new Student("John", 21),
                new Student("Rita", 19), new Student("Sam", 26),
                new Student("Claire", 16) };
        
        System.out.println("Before custom sorting");
        for(Student student : allStudents) {
            System.out.println(student);
        }
        Arrays.sort(allStudents, new StudentNameComparator());
        System.out.println("After custom Sorting");
        for(Student student : allStudents) {
            System.out.println(student);
        }
    }

}

And the output is - 

Before custom sorting
Student [name=John, age=21]
Student [name=Rita, age=19]
Student [name=Sam, age=26]
Student [name=Claire, age=16]
After custom Sorting
Student [name=Claire, age=16]
Student [name=John, age=21]
Student [name=Rita, age=19]
Student [name=Sam, age=26]


Logic in compare() method that we override on implementing Comparator interface is same as compareTo() method that we override on implementing Comparable interface.

Notice how we just gave the array to sort in Arrays.sort() in natural sorting where as we gave the comparator with custom ordering logic as a separate argument to sort method.



Hope this clarifies the difference. Let me know if you still have any doubts or questions.

Java 8 changes in Comparator and Comparable interface

 With introduction of function interface and Lambda expressions in Java 8 there are changes made in comparable and comparator  interfaces as well. 
  • Both comparable and comparator interfaces are functional interfaces.
Just to remind you interfaces can now also have static methods and they do not affect the functional status of an interface.

Now let's visit our old problem of sorting student. As per what we discussed we said let age be used to do natural sorting (used comparable interface for that) and for sorting with name (custom sorting) we use comparator interface. But what if we 1st want to compare using name and if that matches then compare using age. Lets see how that would workout

public class StudentNameComparator implements Comparator<Student> {

    @Override
    public int compare(Student student1, Student student2) {
        int result =  student1.getName().compareTo(student2.getName());
        if (result != 0) return result;
        return student1.getAge() - student2.getAge();
    }

}


Sure that works out but there is a smarter way. One that Java 8 provides. Well see that now.

public class StudentNameComparator implements Comparator<Student> {

    @Override
    public int compare(Student student1, Student student2) {
        Comparator<Student> c = Comparator.comparing(s -> s.getName());
        c = c.thenComparingInt(s -> s.getAge());
        return c.compare(s1, s2);
    }

}


NOTE : comparing static method - Accepts a function that extracts a Comparable sort key from a type T, and returns a Comparator<T> that compares by that sort key.


Related Links


No comments:

Post a comment

t> UA-39527780-1 back to top