介绍
在使用自定义类型或尝试比较不能直接比较的对象时,我们需要使用比较策略。
我们可以简单地构建一个比较策略,但是要使用Comparator
或Comparable
接口
举例
我们以一个足球队为例(我们想按照球队在球员的排名来排列他们)
先创建一个简单的Player
类
public class Player {
private int ranking;
private String name;
private int age;
// constructor, getters, setters
}
创建一个List
存放多个Player
, 并使用Collections.sort
进行排序
List<Player> footballTeam = new ArrayList<>();
Player p1 = new Player(50, "Tom", 27);
Player p2 = new Player(70, "Micale", 29);
Player p3 = new Player(60, "Jerry", 28);
footballTeam.add(p1);
footballTeam.add(p2);
footballTeam.add(p3);
System.out.println("Before Sorting : " + footballTeam);
Collections.sort(footballTeam);
System.out.println("After Sorting : " + footballTeam);
如预期的那样,这里会出现编译时错误:
让我们看一下Collections.sort
的源码:
public static <T extends Comparable<? super T>> void sort(List<T> list) {
list.sort(null);
}
public static <T> void sort(List<T> list, Comparator<? super T> c) {
list.sort(c);
}
我们可以发现,调用
Collections.sotr()
方法时,要么类型变量T
实现了Comparable
接口,表示T
是一种具有可比较能力
的类,要么我们自己实现Compartor
接口(可以理解为创建一种比较策略
),并作为参数传入sort方法。
理解上面的结论后,不难发现为什么会出现上图中的报错,Player
没有实现Comparable
接口,表明Player
不具备比较能力,而我们也没有传入Compartor
(比较策略), 所以 Collections.sort()
根本不知道按照什么规则去排序。
Comparable
Java中一般以able
为结尾的都是接口(Runnable
, Iterable
, AutoCloseable
...),
Comparable
的是一个接口,定义了将对象与相同类型的其他对象进行比较的策略, 我们称这种策略为自然排序。
// 该接口只有一个抽象方法compareTo, 返回一个int值
public interface Comparable<T> {
public int compareTo(T o)
}
因此,为了能够排序–我们必须通过实现Comparable
接口将Player
对象定义为可比较的对象:
public class Player implements Comparable<Player> {
//...
@Override
public int compareTo(Player otherPlayer) {
return (this.getRanking() - otherPlayer.getRanking());
}
}
排序顺序由compareTo()
方法的返回值确定( 0: 表示相等, 1:表示大于, -1: 表示小于)
最终排序如下:
Before Sorting : [{name='Tom'}, {name='Micale'}, {name='Jerry'}]
After Sorting : [{name='Tom'}, {name='Jerry'}, {name='Micale'}]
Compartor
Comparator
接口定义了一个compare(arg1,arg2)
方法,该方法具有两个需要被比较对象的参数,其工作原理类似于Comparable.compareTo()
方法。
@FunctionalInterface
public interface Comparator<T> {
int compare(T o1, T o2)
}
@FunctionalInterface
表明这是一个函数式接口,可以配合Java 8 lambda
使用, 本文后面也会提到
实现
Compartor
接口,并重写compare
方法,就意味着创建了一种新的排序规则(比较器)
创建Compartor
创建一个使用Player
的rank
属性作为排序规则的 Compartor
public class PlayerRankingComparator implements Comparator<Player> {
@Override
public int compare(Player firstPlayer, Player secondPlayer) {
return (firstPlayer.getRanking() - secondPlayer.getRanking());
}
}
同理,可以创建使用Player
的age
属性作为排序规则的Compartor
public class PlayerAgeComparator implements Comparator<Player> {
@Override
public int compare(Player firstPlayer, Player secondPlayer) {
return (firstPlayer.getAge() - secondPlayer.getAge());
}
}
使用Compartor
为了演示这个概念,我们通过在Collections.sort
方法中引入第二个参数来修改Player
的排序规则
通过使用这种方法,我们可以覆盖自然排序规则:
PlayerRankingComparator playerComparator = new PlayerRankingComparator();
Collections.sort(footballTeam, playerComparator);
如果我们想要一个不同的排序顺序,我们只需要更改我们使用的比较器(Compartor
)即可:
PlayerAgeComparator playerComparator = new PlayerAgeComparator();
Collections.sort(footballTeam, playerComparator);
Java 8 Compartors
Java 8通过使用lambda
表达式和compare()
静态工厂方法提供了定义比较器的新途径。
让我们看一下如何使用lambda表达式创建比较器的简单示例:
Comparator<Player> byRanking
= (Player player1, Player player2) -> player1.getRanking() - player2.getRanking();
IDEA会提示你,可以替换成 Comparator.comparingInt
Comparator<Player> byRanking = Comparator.comparingInt(Player::getRanking);
让我们来学习一下 Java 8中的Compartor
介绍
Java 8对Comparator接口进行了一些增强,其中包括一些静态函数,这些静态函数在提出集合的排序顺序时很有用。还可以通过Comparator接口有效地利用Java 8 lambda。
创建 Employee 类
public class Employee {
String name;
int age;
double salary;
long mobile;
// constructors, getters & setters
}
测试数据
Employee e1 = new Employee("Tom", 25, 3000.0, 9922001);
Employee e2 = new Employee("Jerry", 22, 2000.0, 59240001);
Employee e3 = new Employee("Sun", 35, 4000.0, 3924401);
Employee[] employees = new Employee[] {e1, e2, e3};
Comparator.comparing(Function keyExtractor)
Comparator.comparing
静态函数接受keyExtractor
(从变量类型T中提取出指定的key)作为排序键, 然后返回一个按照该排序键作为排序规则的Compartor
(比较器),
public static <T, U extends Comparable<? super U>> Comparator<T> comparing(Function<? super T, ? extends U> keyExtractor)
{
Objects.requireNonNull(keyExtractor);
return (Comparator<T> & Serializable)
(c1, c2) -> keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2));
}
为了了解这一点,让我们使用Employee
中的name
字段作为排序键,并将其方法引用(Employee::getName
)作为Function
类型的参数传递。
Comparator<Employee> employeeNameComparator = Comparator.comparing(Employee::getName);
Arrays.sort(employees, employeeNameComparator);
System.out.println("======>按name排序");
Arrays.stream(employees).forEach(System.out::println);
输出结果:
======>按name排序
Employee{name='Jerry', age=22, salary=2000.0, mobile=59240001}
Employee{name='Sun', age=35, salary=4000.0, mobile=3924401}
Employee{name='Tom', age=25, salary=3000.0, mobile=9922001}
Comparator.comparing(Function keyExtractor, Compartor keyCompartor)
Comparator.comparing
还有一个选项可以通过提供用于为排序键创建自定义排序的Comparator(keyCompartor
)来帮助重写排序键的自然排序:
keyCompartor
: 覆盖排序键的默认排序规则,也可以理解成 为排序键提供的全新的自然排序规则
public static <T, U> Comparator<T> comparing(Function<? super T, ? extends U> keyExtractor, Comparator<? super U> keyComparator)
{
Objects.requireNonNull(keyExtractor);
Objects.requireNonNull(keyComparator);
return (Comparator<T> & Serializable) (c1, c2) -> keyComparator.compare(keyExtractor.apply(c1), keyExtractor.apply(c2));
}
让我们修改上面的测试,通过提供一个Comparator以按降序对名称进行排序作为Comparator.comparing的第二个参数,从而覆盖按名称字段进行排序的自然排序
// 按name排序, 并修改name的自然排序规则
Comparator<Employee> employeeNameComparator2 = Comparator.comparing(Employee::getName, (k1, k2) -> {return k2.compareTo(k1);});
Arrays.sort(employees, employeeNameComparator2);
System.out.println("======>按name降序排序");
Arrays.stream(employees).forEach(System.out::println);
输出结果:
======>按name降序排序
Employee{name='Tom', age=25, salary=3000.0, mobile=9922001}
Employee{name='Sun', age=35, salary=4000.0, mobile=3924401}
Employee{name='Jerry', age=22, salary=2000.0, mobile=59240001}
Compartable vs Compartor
定义类的默认排序规则(或叫做自然排序规则)时, 实现Compartable
是一个不错的选择式。
但是,我们必须问自己,如果我们已经有了Comparable
,为什么还要使用Comparator
?
原因有以下几种:
- 有时,我们无法修改要对其对象进行排序的类的源代码,因此无法使用Comparable
- 使用Compartor使我们避免向类中添加其他代码
- 我们可以定义多种不同的比较策略(Compartor),这在使用Comparable时是不可能的