一、使用情景
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)定制排序优先。