Saturday, November 20, 2010

Java: Comparable/Comparator

Sometimes, in Java, you have a List or some other collection, that you would like to sort. However, the List might be a List of objects with each object possessing multiple properties(fields). Ok, Cut to the Chase.

You have a 'Man' object with the fields, name, height, weight. You would like to sort an ArrayList of men according to one of those fields. I tried telling Java that all Men are born equal, but it didn't work.

There are two ways to do this. In both of these ways, we use the sort method of the Collections class. However, the parameters of the overloaded sort method differ. I will explain more in detail below.

1. Change the Man class to implement the Comparable interface. Then, you would have to implement the compare method(with one parameter). If you want to sort the "Man" objects based on their weight (say, if you are going for NFL picks), put the code in the compare method to compare based on the weights.


Here below, the 'Man' class that implements Comparable interface


package main.java.testing;
public class Man implements Comparable{

//fields
private String name;
private int height;
private int weight; 

//Constructor
public Man(String name, int height, int weight) {
this.name = name;
this.height = height;
this.weight = weight;
}

//This method compares based on the weight of the two "Man" objects
@Override
public int compareTo(Man o) {
return this.height - o.height;
}

//Getters and Setters
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
public int getWeight() {
return weight;
}
public void setWeight(int weight) {
this.weight = weight;
}
}
The Example class with the main method and sample data to sort.

package main.java.testing;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class ManSortingExample {

public static void main(String[] args)
{

List<Man> manList = new ArrayList<Man>();
manList.add(new Man("Shipley, Jordan", 71, 193));
manList.add(new Man("Alem, Rahim", 75, 251));
manList.add(new Man("Beadles, Zane", 76, 310));
manList.add(new Man("Capers, Selvish", 76, 308));
manList.add(new Man("McCoy, Colt", 73, 216));
manList.add(new Man("Peek, Colin", 77, 254));
manList.add(new Man("Verner, Alterraun", 70, 189));

System.out.println("--------------------------------------------------");
System.out.println("BEFORE SORT USING COMPARABLE METHOD OF THE OBJECT");
for(Man m:manList)
{
System.out.println(m.getName() + " " +m.getHeight() + " " +m.getWeight());
}

//Use the sort method of the Collections class
Collections.sort(manList);

System.out.println("--------------------------------------------------");
System.out.println("AFTER SORT USING COMPARABLE OF THE OBJECT(WEIGHT)");
for(Man m:manList)
{
System.out.println(m.getName() + " " +m.getHeight() + " " +m.getWeight());
}
}
}

To sort, we use the one of the sort method of the Collections class. It takes a List(that implements comparable) as a parameter and sorts the List based on that method.
Run the example program and you will get the List sorted by weights(that we coded in the compare method).

The output would be
--------------------------------------------------
BEFORE SORT USING COMPARABLE METHOD OF THE OBJECT
Shipley, Jordan 71 193
Alem, Rahim 75 251
Beadles, Zane 76 310
Capers, Selvish 76 308
McCoy, Colt 73 216
Peek, Colin 77 254
Verner, Alterraun 70 189
--------------------------------------------------
AFTER SORT USING COMPARABLE OF THE OBJECT(WEIGHT)
Verner, Alterraun 70 189
Shipley, Jordan 71 193
McCoy, Colt 73 216
Alem, Rahim 75 251
Beadles, Zane 76 310
Capers, Selvish 76 308
Peek, Colin 77 254


However, after a few days, if you want to sort the List based on Name, you would have to change the Employee Class' compare method again. If you don't want to do that, then you can follow the second approach.

2. In this approach, the objects that we want to sort need not implement any interface. Instead, we write another class that implements the Comparator interface and use that class as a parameter to another of the Collections.sort methods. Example below.

Create, the so called sorting class by implementing the Comparator interface. You would have to override the compare method (with two parameter objects)

Now, change you Example class to add the sorting based on Name too.

package main.java.testing;

import java.util.Comparator;

public class ManSortByName implements Comparator{

@Override
public int compare(Man o1, Man o2) {
return o1.getName().compareTo(o2.getName()); }

}

And now for testing this change:
package main.java.testing;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class ManSortingExample {

public static void main(String[] args)
{

List manList = new ArrayList();
manList.add(new Man("Shipley, Jordan", 71, 193));
manList.add(new Man("Alem, Rahim", 75, 251));
manList.add(new Man("Beadles, Zane", 76, 310));
manList.add(new Man("Capers, Selvish", 76, 308));
manList.add(new Man("McCoy, Colt", 73, 216));
manList.add(new Man("Peek, Colin", 77, 254));
manList.add(new Man("Verner, Alterraun", 70, 189));

System.out.println("--------------------------------------------------");
System.out.println("BEFORE SORT USING COMPARABLE METHOD OF THE OBJECT");
for(Man m:manList)
{
System.out.println(m.getName() + " " +m.getHeight() + " " +m.getWeight());
}

//Use the sort method of the Collections class
Collections.sort(manList);

System.out.println("--------------------------------------------------");
System.out.println("AFTER SORT USING COMPARABLE OF THE OBJECT(WEIGHT)");
for(Man m:manList)
{
System.out.println(m.getName() + " " +m.getHeight() + " " +m.getWeight());
}

//Sort method that takes two parameters, the second being the comparator class
Collections.sort(manList, new ManSortByName());

System.out.println("--------------------------------------------------");
System.out.println("AFTER SORT USING COMPARATOR CLASS (NAME)");
for(Man m:manList)
{
System.out.println(m.getName() + " " +m.getHeight() + " " +m.getWeight());
}

}

}
The output would be:
--------------------------------------------------
BEFORE SORT USING COMPARABLE METHOD OF THE OBJECT
Shipley, Jordan 71 193
Alem, Rahim 75 251
Beadles, Zane 76 310
Capers, Selvish 76 308
McCoy, Colt 73 216
Peek, Colin 77 254
Verner, Alterraun 70 189
--------------------------------------------------
AFTER SORT USING COMPARABLE OF THE OBJECT(WEIGHT)
Verner, Alterraun 70 189
Shipley, Jordan 71 193
McCoy, Colt 73 216
Alem, Rahim 75 251
Beadles, Zane 76 310
Capers, Selvish 76 308
Peek, Colin 77 254
--------------------------------------------------
AFTER SORT USING COMPARATOR CLASS (NAME)
Alem, Rahim 75 251
Beadles, Zane 76 310
Capers, Selvish 76 308
McCoy, Colt 73 216
Peek, Colin 77 254
Shipley, Jordan 71 193
Verner, Alterraun 70 189



To sort, we use another of the sort method of the Collections class. It takes a List(that NEED NOT implement comparable) as the first parameter and the Comparator class(ManSortByName in this case) and sorts the List based on that compare method of the Comparator class(ManSortByName in this case).
Run the example program and you will get the List sorted by name(that we coded in the compare method of ManSortByName class).
That's it. Now you can just change the compare method of the ManSortByName class if you need to change the sort criteria or create another class that implements the Comparator class (say ManSortByHeight) and use it as a parameter to the Collections.sort method.

I carefully selected 'Man' object instead of 'Woman' object as people would think I am objectifying women. You are welcome.