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 接口更强大,因为它允许你对任何类型的对象进行排序,即使你不能更改其代码。