Showing posts with label Comparison. Show all posts
Showing posts with label Comparison. Show all posts

Sunday, 15 May 2011

Implementing Comparator interface

Suppose we have already decided natural ordering of the object, but for some reason we have to compare 2 objects based on some other fields in it.

Eg. Take the case of employee. We have already written compareTo() method for it. By this we have decided its natural ordering. Now, if we need to sort using other fields of the employee, we’ll have to change the Employee class’s compareTo() method to use those fields. But then we’ll loose this empId based sorting mechanism. This is not a good alternative if we need to sort using different fields at different occasions. But no need to worry; Comparator is there to save us.

By writing a class that implements the java.util.Comparator interface, you can sort Employees using any field as you wish even without touching the Employee class itself; Employee class does not need to implement java.lang.Comparable or java.util.Comparator interface.

Contract of compare method

java.util.Comparator: int compare(Object o1, Objecto2)
This method compares o1 and o2 objects. Returned int value has the following meanings.

  1. positive – o1 is greater than o2
  2. zero – o1 equals to o2
  3. negative – o1 is less than o1

Example

Sorting by name field

Following EmpSortByName class is used to sort Employee instances according to the name field. In this class, inside the compare() method sorting mechanism is implemented. In compare() method we get two Employee instances and we have to return which object is greater.

public class EmpSortByName implements Comparator<Employee>{

public int compare(Employee o1, Employee o2) {
return o1.getName().compareTo(o2.getName());
}
}


Watch out: Here, String class’s compareTo() method is used in comparing the name fields (which are Strings).
Now to test this sorting mechanism, you must use the Collections.sort(List, Comparator) method instead of Collections.sort(List) method. Now change the TestEmployeeSort class as follows. See how the EmpSortByName comparator is used inside sort method.

Using the above Comparator for sorting:

import java.util.*;

public class TestEmployeeSort {

public static void main(String[] args) {

List coll = Util.getEmployees();
//Collections.sort(coll);
//use Comparator implementation
Collections.sort(coll, new EmpSortByName());
printList(coll);
}

private static void printList(List<Employee> list) {
System.out.println("EmpId\tName\tAge");
for (Employee e: list) {
System.out.println(e.getEmpId() + "\t" + e.getName() + "\t" + e.getAge());
}
}
}


Sorting by empID field


Even the ordering by empId (previously done using Comparable) can be implemented using Comparator; following class does that.

public class EmpSortByEmpId implements Comparator<Employee>{

public int compare(Employee o1, Employee o2) {
return o1.getEmpId() - o2.getEmpId();
}
}


Comparator vs Comparable interface


The primary use of comparators is to pass them to something that does sorting, either one of the explicit sort methods, or to a data structure than implicitly sorts (eg, TreeSet or TreeMap).


Comparators are not needed for arrays of primitive values, or arrays of collections of objects that have a natural ordering, eg, String, BigInteger, etc.

Implementing compareTo() : Sorting in natural ordering using compareTo()

Defining the criteria for sorting

Suppose we have list of Employees. We have to sort them. By default, you can't sort it, until you define some criteria. Say you define sorting as first name of employee, or you may say sort on the basis of employee id or their salary. So first thing is we have to define some criteria for sorting. This defines "natural ordering" on which sort can be done.

How to define "natural order" in java?

For this, we have to implement Comparable interface, which has compareTo method, which decides how we put natural ordering on the object.

Example:

Employee’s natural ordering would be done according to the employee id. For that, above Employee class must be altered to add the comparing ability as follows.

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

/**
* Compare a given Employee with this object.
* If employee id of this object is
* greater than the received object,
* then this object is greater than the other.
*/
public int compareTo(Employee o) {
return this.empId - o.empId ;
}
….
}

So this compareTo() method does the trick of implementing natural ordering. So if we have 2 Employees, emp1.id – emp2.id is negative, then emp1 falls before emp2 in this ordering. We can say it like this:

emp1.id < emp2.id – emp1 falls before emp2 in natural ordering

emp1.id == emp2.id – emp1 falls with emp2 in natural ordering

emp1.id > emp2.id – emp1 falls after emp2 in natural ordering

 

Sorting the employee list:

 

We’ll write a class to test this natural ordering mechanism. Following class use the Collections.sort(List) method to sort the given list in natural order.

import java.util.*;

public class TestEmployeeSort {

public static void main(String[] args) {
List coll = Util.getEmployees();
Collections.sort(coll); // sort method
printList(coll);
}

private static void printList(List<Employee> list) {
System.out.println("EmpId\tName\tAge");
for (Employee e: list) {
System.out.println(e.getEmpId() + "\t" + e.getName() + "\t" + e.getAge());
}
}
}


Similarly to sort on the basis of name : write compareTo like this:


public int compareTo(Employee other) {
return name.compareTo(o2.getName());
//here compareTo() method of String is called
}

Implementing compareTo() using commons-lang

Here CompareToBuilder is used to implement compareTo();

public int compareTo(Person person) {
return new CompareToBuilder()
append(this.firstName, person.firstName)
append(this.lastName, person.firstName)
toComparison();
}


Also, there is yet another reflection option:


public int compareTo(Object o) {
return CompareToBuilder.reflectionCompare(this, o);
}

Tuesday, 1 February 2011

Implementing null-safe compareTo

// primarily by name, secondarily by value; null-safe; case-insensitive
public int compareTo(final Metadata other) {
int result = nullSafeStringComparator(this.name, other.name);
if (result != 0) {
return result;
}

return nullSafeStringComparator(this.value, other.value);
}

public static int nullSafeStringComparator(final String one, final String two) {
if (one == null ^ two == null) {
return (one == null) ? -1 : 1;
}

if (one == null && two == null) {
return 0;
}

return one.compareToIgnoreCase(two);
}