Java中的Comparable接口和Comparator
阅读原文时间:2021年04月20日阅读:2

    在现实世界中,对象通常是可以比较的.例如,爸爸的车比妈妈的车要贵,这本字典比那本字典要薄,Granny要比Auntie Mollie大,等等。在写面向对象的程序时,通常需要比较一个类的两个实例。一旦实例可以比较,它们就可以被排序。举个例子,给出两个员工,你也许想知道哪一个员工在公司呆的时间要长些。或者,通过first name搜索出来的人你希望通过年龄来对他们排序。这篇文章教你怎么样通过实现java.lang.Comaparable接口和java.lang.Comparator来设计你的类使得实例可以比较.

    大部分的程序员知道怎么样通过java.util.Arrays类中的sort方法来对String数组中的元素进行排序。对于ArrayList中的String实例,你可以通过java.util.Collections类中的方法来对它们进行排序。第一个例子展现了如何通过Arrays.sort方法来对String实例进行排序

Listing 1: Sorting String Instances Using Arrays.sort import 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. Listing 2: Sorting String Instances Using Collections.sort import 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. Listing 3: The Person Class class 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);

怎么样通过年龄和名字来比较这些Person实例呢?运用java.util.Arrays类的sort方法,像这样

Arrays.sort(persons);

将会抛出ClassCastException.

当然,你可以写一段你自己的代码通过运用算法例如快速排序、冒泡排序或者其他的来对它们进行排序。但是,这是不实际的。最好的解决办法就是实现java.lang.Comparable接口

Using the java.lang.Comparable Interface

实现Comparable接口可以使得你的类实例可以比较。这个接口有一个方法:compareto.这个方法决定了怎么样比较类的两个实例。这个方法具体是:

public int compareTo(Object o)

这个compareto方法能够接收一个对象作为参数,所以你能传递给它任何类型的对象。然而你需要实现的是比较两个同类型的实例。通过它去比较一只大象和一只蚂蚁是说不通的。因此,如果这个方法的参数的类型不和你的类一样那么就会抛出java.lang.ClassCastException异常

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. Listing 4: The Person Class That Implements the Comparable Interface package 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; } } Listing 5: The comparable.ex01.Testing Class package 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

Using the java.util.Comparator Class Implementing 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. Listing 6: The comparable.ex02.Person Class package 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; } } Listing 7: The comparable.ex02.LastNameComparator Class package 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); } } Listing 8: The comparable.ex02.FirstNameComparator Class package 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); } } Listing 9: The comparable.ex02.Testing Class package 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 Integrating Comparators in Comparable Classes 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. Listing 10: The comparable.ex03.Person Class package 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); } }; } Listing 11: The comparable.ex03.Testing Class package 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); Summary 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.