小白养成记——Java比较器Comparable和Comparator

一、使用情景

1.  调用Arrays.sort()方法或Collections.sort()方法对自定义类的对象排序

以Arrays.sort()为例。假定有如下自定义的Person类

 1 public class Person {
 2 
 3     private String name;
 4     private Integer age;
 5 
 6     public Person() {}
 7 
 8     public Person(String name, Integer age) {
 9         this.name = name;
10         this.age = age;
11     }
12 
13     //Getter and Setter
14 
15     @Override
16     public String toString() {
17         return "Person{" +
18                 "name='" + name + '\'' +
19                 ", age=" + age +
20                 '}';
21     }
22 
23     @Override
24     public boolean equals(Object o) {
25         if (this == o) return true;
26         if (o == null || getClass() != o.getClass()) return false;
27 
28         Person person = (Person) o;
29 
30         if (!Objects.equals(name, person.name)) return false;
31         return Objects.equals(age, person.age);
32 
33     }
34 
35     @Override
36     public int hashCode() {
37         int result = name != null ? name.hashCode() : 0;
38         result = 31 * result + (age != null ? age.hashCode() : 0);
39         return result;
40     }
41 }
compare.beans.Person

创建一个一维数组来存放多个Person对象,然后将该数组作为参数调用数组工具类Arrays的静态方法sort(Object[] a),最后遍历

 1 @Test
 2 public void test3() {
 3     Person[] persons = new Person[]{
 4             new Person("Mike", 23),
 5             new Person("Alice", 19),
 6             new Person("Jerry", 17)
 7     };
 8     Arrays.sort(persons);
 9     for (Person person : persons) {
10         System.out.println(person);
11     }
12 }
compare.test.CompareTest

则会出现java.lang.ClassCastException异常,提示Person不能转换为Comparable,这便需要用到比较器。

2. 在TreeSet容器容器中添加自定义类的对象

还是以上文中的Person类为例。创建一个TreeSet容器,将多个Person对象添加其中,查看是否添加成功

1 @Test
2 public void test4() {
3     Set setOfPersons = new TreeSet();
4     setOfPersons.add(new Person("Mike", 23));
5     setOfPersons.add(new Person("Alice", 19));
6     setOfPersons.add(new Person("Jerry", 17));
7     System.out.println("setOfPersons.size() = " + setOfPersons.size());
8 }
compare.test.CompareTest

可以看到还是会出现java.lang.ClassCastException异常,提示Person不能转换为Comparable。

二、使用方式

1. 自然排序——使用java.lang.Comparable<T>接口

在自定义类时可以让其实现这个Comparable接口,重写接口中的int compareTo(T o)方法,这样该类便“具有了比较的功能”。如创建一个实现了Comparable接口的Person类

 1 public class Person implements Comparable<Person> {
 2 
 3     private String name;
 4     private Integer age;
 5 
 6     public Person() {}
 7 
 8     public Person(String name, Integer age) {
 9         this.name = name;
10         this.age = age;
11     }
12 
13     public String getName() {
14         return name;
15     }
16 
17     public void setName(String name) {
18         this.name = name;
19     }
20 
21     public Integer getAge() {
22         return age;
23     }
24 
25     public void setAge(Integer age) {
26         this.age = age;
27     }
28 
29     @Override
30     public String toString() {
31         return "Person{" +
32                 "name='" + name + '\'' +
33                 ", age=" + age +
34                 '}';
35     }
36 
37     @Override
38     public boolean equals(Object o) {
39         if (this == o) return true;
40         if (o == null || getClass() != o.getClass()) return false;
41 
42         Person person = (Person) o;
43 
44         if (!Objects.equals(name, person.name)) return false;
45         return Objects.equals(age, person.age);
46 
47     }
48 
49     @Override
50     public int hashCode() {
51         int result = name != null ? name.hashCode() : 0;
52         result = 31 * result + (age != null ? age.hashCode() : 0);
53         return result;
54     }
55 
56     /**
57      * 在这里定义比较的方式,如先比较年龄,若年龄相同则再比较姓名
58      * 按照升序排序的规则为:
59      *      若当前对象的指定属性大于传入的待比较对象的指定属性,则返回一个正整数(如1)
60      *      若当前对象的指定属性小于传入的待比较对象的指定属性,则返回一个负整数(如-1)
61      *      若当前对象的指定属性等于传入的待比较对象的指定属性,则返回0
62      * @param anotherPerson 传入的待比较对象
63      * @return 返回一个整数作为比较结果的参考
64      */
65     @Override
66     public int compareTo(Person anotherPerson) {
67         
68         int differ = this.getAge() - anotherPerson.getAge();
69         if (differ != 0) {
70             return differ / Math.abs(differ);
71         }
72         return this.getName().compareTo(anotherPerson.getName());
73     }
74 }
compare.beans.Person

这样的Person类便具有了“比较”的功能,在调用sort()方法或添加到TreeSet容器中时便不会再出现异常,无需再更改代码。显然,最值得关注的地方就是compareTo(T o)方法的实现。由于String以及基本数据类型的包装类都已经实现了带泛型的Comparable接口,类的属性一般来说也都是这些类型,所以在写compareTo(T o)方法的方法体时可以直接调用属性的compareTo(T o)方法,省去条件分支语句。

2. 定制排序——使用java.util.Comparator<T>接口

如果需要让Person类的对象在不同情景下使用不同的排序方式,那么通过让Person类实现Comparable接口的方式便显得不具有灵活性了,此时便可以使用Comparator接口。

Comparator接口的使用与Comparable有所不同,它并不是要让自定义的类来实现,而是在需要排序的地方临时创建一个Comparator的实现类,并实现其中的int compare(T o1, T o2)方法,对传入的对象实现排序(比较)。由于这个实现类只是“临时”的,所以使用一个匿名类即可。

数组工具类Arrays的sort()方法是重载的,除了sort(Object[] a)外还有一个sort(T[] a, Comparator<? super T> c),使用后者,传入一个Comparator接口的匿名实现类便可以实现定制排序,如

 1 @Test
 2 public void test3() {
 3     Person[] persons = new Person[]{
 4             new Person("Mike", 23),
 5             new Person("Alice", 19),
 6             new Person("Jerry", 17)
 7     };
 8     Arrays.sort(persons, new Comparator<Person>() {
 9         @Override
10         public int compare(Person person1, Person person2) {
11             int differ = person1.getName().compareTo(person2.getName());
12             if (differ != 0) {
13                 return differ;
14             }
15             return person1.getAge().compareTo(person2.getAge());
16         }
17     });
18     for (Person person : persons) {
19         System.out.println(person);
20     }
21 }
compare.test.CompareTest

这样便会按照匿名类里的compare(T o1, T o2)方法指定的方式来进行排序,无论Person类是否实现了Comparable接口。

对TreeSet的处理类似。可以在创建TreeSet容器时调用其含参的构造器,传入一个Comparator接口的匿名实现类,如

 1 @Test
 2 public void test4() {
 3     Set setOfPersons = new TreeSet(new Comparator<Person>() {
 4         @Override
 5         public int compare(Person person1, Person person2) {
 6             int differ = person1.getName().compareTo(person2.getName());
 7             if (differ != 0) {
 8                 return differ;
 9             }
10             return person1.getAge().compareTo(person2.getAge());
11         }
12     });
13     setOfPersons.add(new Person("Mike", 23));
14     setOfPersons.add(new Person("Alice", 19));
15     setOfPersons.add(new Person("Jerry", 17));
16     System.out.println("setOfPersons.size() = " + setOfPersons.size());
17 }
compare.test.CompareTest

三、二者的区分

由于这两个接口名以及其中的方法名都含有compare前缀,因此初次接触很容易混淆。二者的区别有如下几点

1)接口Comparable<T>位于java.lang包下,而接口Comparator<T>位于java.util包下。

2)Comparable<T>接口可以让自定义类来实现,从而使该的类具有“顺序性”;而Comparator<T>接口并不是让自定义类来实现的,只需要在需要用到排序的地方临时创建一个指定泛型的实现类,对指定类型的对象进行排序。

3)Comparable<T>接口的compareTo(T o)方法中含有一个形参,自定义类的对象可以调用;而Comparator<T>接口的compare(T o1, T o2)方法中含有两个形参,一般来说不需要手动调用。

4)定制排序优先。

posted @ 2019-04-13 20:27  Sept4_桃李宿江南  阅读(223)  评论(0编辑  收藏  举报