Java 高级排序(Comparator 和 Comparable)
Java 高级排序
在“列表排序”章节中,您学习了如何按字母顺序和数字顺序对列表进行排序,但如果列表中包含对象呢?
为了对对象进行排序,您需要指定一个规则来决定对象的排序方式。例如,如果您有一个汽车列表,你可能想按年份排序,规则可以是年份较早的汽车排在前面。
Comparator
和 Comparable
接口允许你指定用于排序对象的规则。
能够指定排序规则还允许您改变字符串和数字的排序方式。
Comparator(比较器)
实现了 Comparator
接口的对象被称为比较器。
Comparator
接口允许你创建一个包含 compare()
方法的类,该方法比较两个对象以决定哪个在列表中应排在前面。
compare()
方法应返回一个数字,该数字:
- 为负数时,表示第一个对象应在列表中排在前面。
- 为正数时,表示第二个对象应在列表中排在前面。
- 为零时,表示顺序无关紧要。
实现 Comparator
接口的类可能如下所示:
// 按年份排序 Car 对象 class SortByYear implements Comparator { public int compare(Object obj1, Object obj2) { // 确保对象是 Car 对象 Car a = (Car) obj1; Car b = (Car) obj2; // 比较对象 if (a.year < b.year) return -1; // 第一辆车的年份更小 if (a.year > b.year) return 1; // 第一辆车的年份更大 return 0; // 两辆车的年份相同 } }
要使用比较器,将其作为参数传递给排序方法:
// 使用比较器对汽车进行排序 Comparator myComparator = new SortByYear(); Collections.sort(myCars, myComparator);
下面是一个使用比较器按年份排序汽车列表的完整示例:
import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; // 定义一个 Car 类 class Car { public String brand; // 品牌 public String model; // 型号 public int year; // 年份 // 构造函数 public Car(String b, String m, int y) { brand = b; model = m; year = y; } } // 创建一个比较器 class SortByYear implements Comparator { // 实现 compare 方法 public int compare(Object obj1, Object obj2) { // 确保传入的对象是 Car 类型 Car a = (Car) obj1; Car b = (Car) obj2; // 比较两辆车的年份 if (a.year < b.year) return -1; // 第一辆车年份更小 if (a.year > b.year) return 1; // 第一辆车年份更大 return 0; // 两辆车年份相同 } } public class Main { public static void main(String[] args) { // 创建一个汽车列表 ArrayList<Car> myCars = new ArrayList<Car>(); myCars.add(new Car("BMW", "X5", 1999)); myCars.add(new Car("Honda", "Accord", 2006)); myCars.add(new Car("Ford", "Mustang", 1970)); // 使用比较器对汽车进行排序 Comparator myComparator = new SortByYear(); Collections.sort(myCars, myComparator); // 显示排序后的汽车 for (Car c : myCars) { System.out.println(c.brand + " " + c.model + " " + c.year); } } }
使用 Lambda 表达式
为了简化代码,比较器可以被替换为具有与 compare()
方法相同参数和返回值的 Lambda 表达式:
Collections.sort(myCars, (obj1, obj2) -> { Car a = (Car) obj1; Car b = (Car) obj2; if (a.year < b.year) return -1; if (a.year > b.year) return 1; return 0; });
特殊排序规则
比较器还可以用于为字符串和数字制定特殊的排序规则。在此例中,我们使用比较器将所有偶数列在奇数之前:
import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; // 定义一个比较器类 SortEvenFirst,实现 Comparator 接口 class SortEvenFirst implements Comparator { // 实现 compare 方法,用于比较两个对象 public int compare(Object obj1, Object obj2) { // 确保传入的对象是 Integer 类型 Integer a = (Integer) obj1; Integer b = (Integer) obj2; // 检查每个数字是否为偶数 // 一个数字如果除以 2 的余数为 0,则为偶数 boolean aIsEven = (a % 2) == 0; boolean bIsEven = (b % 2) == 0; // 如果两个数字同为偶数或同为奇数,则按正常排序规则比较 if (aIsEven == bIsEven) { if (a < b) return -1; // a小于b,返回-1 if (a > b) return 1; // a大于b,返回1 return 0; // a等于b,返回0 } else { // 如果 a 是偶数,则 a 排在前面,否则 b 排在前面 if (aIsEven) { return -1; // a 是偶数,返回 -1,表示 a 排在前面 } else { return 1; // b 是偶数,返回 1,表示 b 排在前面 } } } } public class Main { public static void main(String[] args) { // 创建一个 Integer 类型的 ArrayList ArrayList<Integer> myNumbers = new ArrayList<Integer>(); // 向列表中添加一些整数 myNumbers.add(33); myNumbers.add(15); myNumbers.add(20); myNumbers.add(34); myNumbers.add(8); myNumbers.add(12); // 创建一个 SortEvenFirst 比较器实例 Comparator myComparator = new SortEvenFirst(); // 使用该比较器对列表进行排序 Collections.sort(myNumbers, myComparator); // 遍历并打印排序后的列表 for (int i : myNumbers) { System.out.println(i); } } }
Comparable 接口
Comparable
接口允许对象通过 compareTo()
方法指定其自身的排序规则。
compareTo()
方法接受一个对象作为参数,并将可比较对象与该参数进行比较,以决定哪个在列表中应排在前面。
与比较器类似,compareTo()
方法返回一个数字,该数字:
- 为负数时,表示可比较对象应在列表中排在前面。
- 为正数时,表示另一个对象应在列表中排在前面。
- 为零时,表示顺序无关紧要。
许多原生 Java 类(如 String
和 Integer
)都实现了 Comparable
接口。
这就是为什么字符串和数字不需要比较器就可以进行排序的原因。
实现了 Comparable
接口的对象可能如下所示:
class Car implements Comparable { public String brand; // 品牌 public String model; // 型号 public int year; // 年份 // 定义此对象如何与其他对象进行比较 public int compareTo(Object obj) { Car other = (Car) obj; // 将传入的对象强制转换为Car类型 if (year < other.year) return -1; // 如果此对象的年份小于另一个对象的年份,则返回 -1,表示此对象较小 if (year > other.year) return 1; // 如果此对象的年份大于另一个对象的年份,则返回 1,表示此对象较大 return 0; // 如果两个对象的年份相同,则返回 0,表示两个对象相等(在年份上) } }
下面是与之前相同的示例,但使用 Comparable
接口而不是比较器:
import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; // 定义一个可比较的 Car 类 class Car implements Comparable<Car> { public String brand; // 品牌 public String model; // 型号 public int year; // 年份 // 构造方法,用于初始化 Car 对象 public Car(String b, String m, int y) { brand = b; model = m; year = y; } // 实现 compareTo 方法,决定此对象如何与其他 Car 对象进行比较 @Override public int compareTo(Car other) { if (year < other.year) return -1; // 如果此车的年份小于另一辆车的年份,则返回 -1,表示此车较小 if (year > other.year) return 1; // 如果此车的年份大于另一辆车的年份,则返回 1,表示此车较大 return 0; // 如果两车的年份相同,则返回 0,表示两车相等(在年份上) } } public class Main { public static void main(String[] args) { // 创建一个汽车列表 ArrayList<Car> myCars = new ArrayList<Car>(); myCars.add(new Car("BMW", "X5", 1999)); myCars.add(new Car("Honda", "Accord", 2006)); myCars.add(new Car("Ford", "Mustang", 1970)); // 对汽车进行排序 Collections.sort(myCars); // 显示汽车列表 for (Car c : myCars) { System.out.println(c.brand + " " + c.model + " " + c.year); } } }
常见的排序技巧
最直观的按自然顺序排序两个数字的方法是编写如下代码:
if(a.year < b.year) return -1; // a 小于 b if(a.year > b.year) return 1; // a 大于 b return 0; // a is equal to b
但实际上,这可以用一行代码完成:
return a.year - b.year;
这个技巧还可以用于轻松实现反向排序:
return b.year - a.year;
Comparator vs. Comparable
比较器(comparator)是一个具有一个方法的对象,用于比较两个不同的对象。
可比较对象(comparable)是一个可以与其他对象进行比较的对象。
在可能的情况下,使用 Comparable
接口更容易,但 Comparator
接口更强大,因为它允许你对任何类型的对象进行排序,即使你不能更改其代码。