In real life, objects are often comparable. For example, Dad's car is more
expensive than Mom's, this dictionary is thicker than those books, Granny is
older than Auntie Mollie (well, yeah, living objects, too, are comparable), and
so forth. In writing object-oriented programs, there are often needs to compare
instances of the same class. And once instances are comparable, they can be
sorted. As an example, given two Employees, you may want to know
which one has stayed in the organization longer. Or, in a search method for
Person instances with a first name of Larry, you may want to
display search results sorted by age. This article teaches you how to design
your class to make its instances comparable by using the
java.lang.Comparable and java.util.Comparator
interfaces and presents three examples that illustrate both interfaces.
Most Java programmers know how to sort the elements of a String
array by using the sort method of java.util.Arrays.
For String instances in an ArrayList, you can sort
them with the sort method of the
java.util.Collections class. The code in Listing 1 shows how to
use Arrays.sort to order String instances.
String Instances Using
Arrays.sortimport java.util.Arrays;
.
.
.
String animals[] = new String[4];
animals[0] = "snake";
animals[1] = "kangaroo";
animals[2] = "wombat";
animals[3] = "bird";
for (int i=0; i<4; i++) {
System.out.println("animal " + i + " : " + animals[i]);
}
Arrays.sort(animals);
for (int i=0; i<4; i++) {
System.out.println("animal " + i + " : " + animals[i]);
}
If you run the program, the first for loop gives you the name of animals as follows:
animal 0 : snake
animal 1 : kangaroo
animal 2 : wombat
animal 3 : bird
And, the second for loop prints the animals sorted alphabetically.
animal 0 : bird
animal 1 : kangaroo
animal 2 : snake
animal 3 : wombat
With the java.util.Collections class's sort
method, you can sort String instances in an
ArrayList, as shown in Listing 2.
String Instances Using
Collections.sortimport java.util.ArrayList;
import java.util.Collections;
.
.
.
ArrayList insects = new ArrayList();
insects.add("mosquito");
insects.add("butterfly");
insects.add("dragonfly");
insects.add("fly");
int size = insects.size();
for (int i=0; i<size; i++) {
System.out.println("insect " + i + " : " + (String) insects.get(i));
}
Collections.sort(insects);
for (int i=0; i<size; i++) {
System.out.println("insect " + i + " : " + (String) insects.get(i));
}
The first for loop in Listing 2 produces the following output:
insect 0 : mosquito
insect 1 : butterfly
insect 2 : dragonfly
insect 3 : fly
The second for loop prints the following:
insect 0 : butterfly
insect 1 : dragonfly
insect 2 : fly
insect 3 : mosquito
However, suppose we have a Person class, as in Listing 3.
Person Classclass Person {
private String firstName;
private String lastName;
private int age;
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
In another part of the program, we construct four instances of the
Person class and populate them with names and ages:
Person[] persons = new Person[4];
persons[0] = new Person();
persons[0].setFirstName("Elvis");
persons[0].setLastName("Goodyear");
persons[0].setAge(56);
persons[1] = new Person();
persons[1].setFirstName("Stanley");
persons[1].setLastName("Clark");
persons[1].setAge(8);
persons[2] = new Person();
persons[2].setFirstName("Jane");
persons[2].setLastName("Graff");
persons[2].setAge(16);
persons[3] = new Person();
persons[3].setFirstName("Nancy");
persons[3].setLastName("Goodyear");
persons[3].setAge(69);
How do we sort these Person instances by age or by name? Using
the java.util.Arrays class' sort method, as in:
Arrays.sort(persons);
will throw a ClassCastException.
You can, of course, write your own code to sort them using an algorithm such
as quick sort, bubble sort, or others, but that's impractical. The
easy solution is to implement the java.lang.Comparable interface.
java.lang.Comparable InterfaceImplement the Comparable interface to make class instances
comparable. This interface has one method, compareTo, which
determines how to compare two instances of the class. The signature of this
method is:
public int compareTo(Object o)
The compareTo method accepts Object, so you can
pass it an instance of any type. However, chances are that you want to make
sure to compare two instances of the same type. It does not make sense to
compare an elephant with an ant, for example. Therefore, you can throw a
java.lang.ClassCastException if the argument of this method is not
the same type as your class.
The compareTo method returns zero if the object passed is equal
to this instance. It returns a positive integer or a negative integer if this
object is greater or smaller than the passed object, respectively.
Let's have a look at the examples in Listing 4 and Listing 5. Listing 4
presents a Person class that implements the
Comparable interface. Notice that a Person object is
older if its age value is greater than the object compared. Listing 5 shows the
Testing class that constructs four instances of the
Person class and sorts them by age. Both classes in Listings 4 and
5 reside in the comparable.ex01 package.
Person Class That Implements the
Comparable Interfacepackage comparable.ex01;
class Person implements Comparable {
private String firstName;
private String lastName;
private int age;
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public int compareTo(Object anotherPerson) throws ClassCastException {
if (!(anotherPerson instanceof Person))
throw new ClassCastException("A Person object expected.");
int anotherPersonAge = ((Person) anotherPerson).getAge();
return this.age - anotherPersonAge;
}
}
comparable.ex01.Testing Classpackage comparable.ex01;
import java.util.Arrays;
import java.util.ArrayList;
public class Testing {
public static void main(String[] args) {
Person[] persons = new Person[4];
persons[0] = new Person();
persons[0].setFirstName("Elvis");
persons[0].setLastName("Goodyear");
persons[0].setAge(56);
persons[1] = new Person();
persons[1].setFirstName("Stanley");
persons[1].setLastName("Clark");
persons[1].setAge(8);
persons[2] = new Person();
persons[2].setFirstName("Jane");
persons[2].setLastName("Graff");
persons[2].setAge(16);
persons[3] = new Person();
persons[3].setFirstName("Nancy");
persons[3].setLastName("Goodyear");
persons[3].setAge(69);
System.out.println("Natural Order");
for (int i=0; i<4; i++) {
Person person = persons[i];
String lastName = person.getLastName();
String firstName = person.getFirstName();
int age = person.getAge();
System.out.println(lastName + ", " + firstName + ". Age:" + age);
}
Arrays.sort(persons);
System.out.println();
System.out.println("Sorted by age");
for (int i=0; i<4; i++) {
Person person = persons[i];
String lastName = person.getLastName();
String firstName = person.getFirstName();
int age = person.getAge();
System.out.println(lastName + ", " + firstName + ". Age:" + age);
}
}
}
The result of the code in Listing 5 is as follows:
Natural Order
Goodyear, Elvis. Age:56
Clark, Stanley. Age:8
Graff, Jane. Age:16
Goodyear, Nancy. Age:69
Sorted by age
Clark, Stanley. Age:8
Graff, Jane. Age:16
Goodyear, Elvis. Age:56
Goodyear, Nancy. Age:69
|
java.util.Comparator ClassImplementing the Comparable interface enables you to define one
way to compare instances of your class. However, objects are sometimes
comparable in many ways. For example, two Person objects may need
to be compared by age or by last/first name. In cases like this, create a
Comparator that defines how to compare two objects. To make
objects comparable in two ways, then you need two comparators.
To create a comparator, write a class that implements the
java.util.Comparator interface--the compare method.
This method has the following signature:
public int compare(Object o1, Object o2)
The compare method returns zero if o1 and
o2 are equal, a negative integer if o1 is less than
o2, and a positive integer if o1 is greater than
o2. Just as in the compareTo method of
Comparable, you define what makes an object equal or less/greater
than another object.
As an example, let's write two comparators for the Person
class. This example consists of four classes, all of which reside in the
comparable.ex02 package. The Person class is similar
to the one in the previous example, and is reprinted in Listing 6 for reading
convenience. Listings 7 and 8 present two comparators of Person
objects (by last name and by first name), and Listing 9 offers the class that
instantiates the Person class and the two comparators.
comparable.ex02.Person Classpackage comparable.ex02;
class Person implements Comparable {
private String firstName;
private String lastName;
private int age;
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public int compareTo(Object anotherPerson) throws ClassCastException {
if (!(anotherPerson instanceof Person))
throw new ClassCastException("A Person object expected.");
int anotherPersonAge = ((Person) anotherPerson).getAge();
return this.age - anotherPersonAge;
}
}
comparable.ex02.LastNameComparator Classpackage comparable.ex02;
import java.util.Comparator;
public class LastNameComparator implements Comparator {
public int compare(Object person, Object anotherPerson) {
String lastName1 = ((Person) person).getLastName().toUpperCase();
String firstName1 = ((Person) person).getFirstName().toUpperCase();
String lastName2 = ((Person) anotherPerson).getLastName().toUpperCase();
String firstName2 = ((Person) anotherPerson).getFirstName().toUpperCase();
if (!(lastName1.equals(lastName2)))
return lastName1.compareTo(lastName2);
else
return firstName1.compareTo(firstName2);
}
}
comparable.ex02.FirstNameComparator
Classpackage comparable.ex02;
import java.util.Comparator;
public class FirstNameComparator implements Comparator {
public int compare(Object person, Object anotherPerson) {
String lastName1 = ((Person) person).getLastName().toUpperCase();
String firstName1 = ((Person) person).getFirstName().toUpperCase();
String lastName2 = ((Person) anotherPerson).getLastName().toUpperCase();
String firstName2 = ((Person) anotherPerson).getFirstName().toUpperCase();
if (!(firstName1.equals(firstName2)))
return firstName1.compareTo(firstName2);
else
return lastName1.compareTo(lastName2);
}
}
comparable.ex02.Testing Classpackage comparable.ex02;
import java.util.Arrays;
import java.util.ArrayList;
public class Testing {
public static void main(String[] args) {
Person[] persons = new Person[4];
persons[0] = new Person();
persons[0].setFirstName("Elvis");
persons[0].setLastName("Goodyear");
persons[0].setAge(56);
persons[1] = new Person();
persons[1].setFirstName("Stanley");
persons[1].setLastName("Clark");
persons[1].setAge(8);
persons[2] = new Person();
persons[2].setFirstName("Jane");
persons[2].setLastName("Graff");
persons[2].setAge(16);
persons[3] = new Person();
persons[3].setFirstName("Nancy");
persons[3].setLastName("Goodyear");
persons[3].setAge(69);
System.out.println("Natural Order");
for (int i=0; i<4; i++) {
Person person = persons[i];
String lastName = person.getLastName();
String firstName = person.getFirstName();
int age = person.getAge();
System.out.println(lastName + ", " + firstName + ". Age:" + age);
}
Arrays.sort(persons, new LastNameComparator());
System.out.println();
System.out.println("Sorted by last name");
for (int i=0; i<4; i++) {
Person person = persons[i];
String lastName = person.getLastName();
String firstName = person.getFirstName();
int age = person.getAge();
System.out.println(lastName + ", " + firstName + ". Age:" + age);
}
Arrays.sort(persons, new FirstNameComparator());
System.out.println();
System.out.println("Sorted by first name");
for (int i=0; i<4; i++) {
Person person = persons[i];
String lastName = person.getLastName();
String firstName = person.getFirstName();
int age = person.getAge();
System.out.println(lastName + ", " + firstName + ". Age:" + age);
}
Arrays.sort(persons);
System.out.println();
System.out.println("Sorted by age");
for (int i=0; i<4; i++) {
Person person = persons[i];
String lastName = person.getLastName();
String firstName = person.getFirstName();
int age = person.getAge();
System.out.println(lastName + ", " + firstName + ". Age:" + age);
}
}
}
If you run the comparable.ex02.Testing class, you can see the
following result:
Natural Order
Goodyear, Elvis. Age:56
Clark, Stanley. Age:8
Graff, Jane. Age:16
Goodyear, Nancy. Age:69
Sorted by last name
Clark, Stanley. Age:8
Goodyear, Elvis. Age:56
Goodyear, Nancy. Age:69
Graff, Jane. Age:16
Sorted by first name
Goodyear, Elvis. Age:56
Graff, Jane. Age:16
Goodyear, Nancy. Age:69
Clark, Stanley. Age:8
Sorted by age
Clark, Stanley. Age:8
Graff, Jane. Age:16
Goodyear, Elvis. Age:56
Goodyear, Nancy. Age:69
The previous example with the Comparator interface works fine.
However, the drawback is that it requires multiple classes. This means more
maintenance work for the users of your comparable class. The next example shows
how to integrate the comparators inside of the comparable class by making the
comparators anonymous classes.
This example has two classes: comparable.ex03.Person (Listing 10) and comparable.ex03.Testing (Listing 11). Note the two
anonymous inner classes at the end of the Person class, and notice
also in the Testing class how comparison is conducted.
comparable.ex03.Person Classpackage comparable.ex03;
import java.util.Comparator;
public class Person implements Comparable {
private String firstName;
private String lastName;
private int age;
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public int compareTo(Object anotherPerson) throws ClassCastException {
if (!(anotherPerson instanceof Person))
throw new ClassCastException("A Person object expected.");
int anotherPersonAge = ((Person) anotherPerson).getAge();
return this.age - anotherPersonAge;
}
public static Comparator LastNameComparator = new Comparator() {
public int compare(Object person, Object anotherPerson) {
String lastName1 = ((Person) person).getLastName().toUpperCase();
String firstName1 = ((Person) person).getFirstName().toUpperCase();
String lastName2 = ((Person) anotherPerson).getLastName().toUpperCase();
String firstName2 = ((Person) anotherPerson).getFirstName().toUpperCase();
if (!(lastName1.equals(lastName2)))
return lastName1.compareTo(lastName2);
else
return firstName1.compareTo(firstName2);
}
};
public static Comparator FirstNameComparator = new Comparator() {
public int compare(Object person, Object anotherPerson) {
String lastName1 = ((Person) person).getLastName().toUpperCase();
String firstName1 = ((Person) person).getFirstName().toUpperCase();
String lastName2 = ((Person) anotherPerson).getLastName().toUpperCase();
String firstName2 = ((Person) anotherPerson).getFirstName().toUpperCase();
if (!(firstName1.equals(firstName2)))
return firstName1.compareTo(firstName2);
else
return lastName1.compareTo(lastName2);
}
};
}
comparable.ex03.Testing Classpackage comparable.ex03;
import java.util.Arrays;
import java.util.ArrayList;
public class Testing {
public static void main(String[] args) {
Person[] persons = new Person[4];
persons[0] = new Person();
persons[0].setFirstName("Elvis");
persons[0].setLastName("Goodyear");
persons[0].setAge(56);
persons[1] = new Person();
persons[1].setFirstName("Stanley");
persons[1].setLastName("Clark");
persons[1].setAge(8);
persons[2] = new Person();
persons[2].setFirstName("Jane");
persons[2].setLastName("Graff");
persons[2].setAge(16);
persons[3] = new Person();
persons[3].setFirstName("Nancy");
persons[3].setLastName("Goodyear");
persons[3].setAge(69);
System.out.println("Natural Order");
for (int i=0; i<4; i++) {
Person person = persons[i];
String lastName = person.getLastName();
String firstName = person.getFirstName();
int age = person.getAge();
System.out.println(lastName + ", " + firstName + ". Age:" + age);
}
Arrays.sort(persons, Person.LastNameComparator);
System.out.println();
System.out.println("Sorted by last name");
for (int i=0; i<4; i++) {
Person person = persons[i];
String lastName = person.getLastName();
String firstName = person.getFirstName();
int age = person.getAge();
System.out.println(lastName + ", " + firstName + ". Age:" + age);
}
Arrays.sort(persons, Person.FirstNameComparator);
System.out.println();
System.out.println("Sorted by first name");
for (int i=0; i<4; i++) {
Person person = persons[i];
String lastName = person.getLastName();
String firstName = person.getFirstName();
int age = person.getAge();
System.out.println(lastName + ", " + firstName + ". Age:" + age);
}
Arrays.sort(persons);
System.out.println();
System.out.println("Sorted by age");
for (int i=0; i<4; i++) {
Person person = persons[i];
String lastName = person.getLastName();
String firstName = person.getFirstName();
int age = person.getAge();
System.out.println(lastName + ", " + firstName + ". Age:" + age);
}
}
}
The result of the Testing class is the same as the previous
example. However, note that the comparators are inside of the Person
class. To sort instances of the Person class by last name, you
just need to use:
Arrays.sort(persons, Person.LastNameComparator);
To sort them by first name:
Arrays.sort(persons, Person.LastNameComparator);
This article has demonstrated how to make class instances comparable and
sortable. Example 1 shows that implementing the compareTo method
of the java.lang.Comparable interface is the easiest solution. To
compare instances in multiple ways, create comparators by implementing the
java.util.Comparator class, as demonstrated in Example 2. For
better maintenance, you can embed the comparators as anonymous classes in your
comparable class, as shown in Example 3.
Budi Kurniawan is a senior J2EE architect and author.
Return to ONJava.com.
Copyright © 2009 O'Reilly Media, Inc.