Monday, November 05, 2007

Comparator or Comparable and more than one Ordering Implementation

To impose ordering on Java objects, you can either implement Comparable or Comparator. Which one to choose? Well, here is my five cents on the issue. When comparing the two approaches, I will use this little simple Java class:
public class Task {
private Integer priority;
private String description;

public Task(Integer priority, String description) {
this.priority = priority;
this.description = description;
}

public String toString() {
return String.format("Task[%d,%s]", priority, description);
}
}

Comparable
Is implemented to express what the docs mention as the implementing class' natural ordering. It is implemented on the class, that is to be ordered, and as such, it can only provide one single ordering implementation. In the example above, I will have to choose, if I want to order on priority or on description. A sample implementation could be like this:
public class Task implements Comparable<Task> {
...

public int compareTo(Task o) {
return priority.compareTo(o.priority);
}

...
}

which compares on priority and only on priority. I can then sort instances in a List like this:
        List<Task> listOfTasks = new ArrayList<Task>() {{
add(new Task(1, "Shop For Christmas Gifts"));
add(new Task(2, "Bring World Peace"));
add(new Task(3, "Make Love, Not War"));
}};

Collections.sort(listOfTasks);

Nice and easy, but I often go with the other implementation (Comparator), as it gives me the freedom to provide more than one implementation. Here is how:

Comparator
Where Comparable was for one single, natural ordering, an implementation of Comparator represents a total ordering of instances of a class. You can provide more than one implementation of Comparator, and then choose when sorting, which one you want to sort on.

I often go for a design, where I put the Comparator implementations as public static classes on the class, that they are implemented to sort on. Like this:
public class Task {
...

public static class ByPriorityComparator implements Comparator<Task> {
public int compare(Task t1, Task t2) {
return t1.priority.compareTo(t2.priority);
}
}

public static class ByDescriptionComparator implements Comparator<Task> {
public int compare(Task t1, Task t2) {
return t1.description.compareTo(t2.description);
}
}

...
}

I can then sort instances in a List like this:
        List<Task> listOfTasks = new ArrayList<Task>() {{
add(new Task(1, "Shop For Christmas Gifts"));
add(new Task(2, "Bring World Peace"));
add(new Task(3, "Make Love, Not War"));
}};

Collections.sort(listOfTasks, new ByPriorityComparator());

// or...

Collections.sort(listOfTasks, new ByDescriptionComparator());

5 comments:

Craig Condit said...

You could extend the idea even a bit further and use static instances for the Comparators:

public static final Comparator<Task> BY_PRIORITY = new ByPriorityComparator();

Then sort with:

Collections.sort(listOfTasks, Task.BY_PRIORITY);

This makes the calling code a little easier to read, and avoids unnecessary object creation as well.

Per Olesen said...

@craig: Yeah, good idea.

Also, I have actually sometimes done this, on the class:

public class Task {
...

public static class ByPriorityComparator implements Comparator<Task> {
public int compare(Task t1, Task t2) {
return t1.priority.compareTo(t2.priority);
}

public static void sort(List<Task> tasks) {
Collections.sort(tasks, new ByPriorityComparator());
}

}

That is, put a static "sort" on the Comparator impl, and then sort elsewhere like this:

Task.ByPriorityComparator.sort(taskList);

:-)

Shailendra said...

Well you can also use commons BeanUtil and make a generic Comparator where you can specify the property name and
get your objects sorted based on that property. :)

Per Olesen said...

@shailendre: Interesting idea!

janko said...

Nice article! How about a combination of Craig's and Per's idea:

public class Task {
...

public static enum TaskComparators implements Comparator<Task> {
BY_PRIORITY {
public int compare(final Task o1, final Task o2) {
return o1.priority.compareTo(o2.priority);
}
public void sort(List<Task> l) {
Collections.sort(l, BY_PRIORITY);
}
},
BY_DESCRIPTION {
public int compare(final Task o1, final Task o2) {
return o1.description.compareTo(o2.description);
}
public void sort(List<Task> l) {
Collections.sort(l, BY_DESCRIPTION);
}
};

public abstract void sort(List<Task> l);
}
}

You can avoid the unnecessary object creation and have the static sort method to use like

TaskComparators.BY_PRIORITY.sort(taskList);