在项目开发中,我们经常要对一组数据进行排序,或者升序或者降序,在Java中排序有多种方式,最土的方法就是自己写排序算法,比如冒泡排序、快速排序、二叉树排序等,但一般不要自己写,JDK已经为我们提供了很多的排序算法,我们采用“拿来主义”就成了。
在Java中,要想给数据排序,有两种实现方式,一种是实现Comparable接口,一种是实现Comparator接口,这两者有什么区别呢?我们来看一个身边的例子,就比如给公司职员排序吧,最经常使用的是按照工号排序,先定义一个职员类代码,如下所示:
class Employee implements Comparable<Employee>{
//id是根据进入公司的先后顺序编码的
private int id;
//姓名
private String name;
//职位
private Position position;
public Employee(int_id, String_name, Position_position){
id=_id;
name=_name;
position=_position;
}
/*id、name、position的getter/setter方法省略*/
//按照id号排序,也就是资历的深浅排序
@Override
public int compareTo(Employee o){
return new CompareToBuilder()
.append(id, o.id).toComparison();
}
@Override
public String toString(){
return ToStringBuilder.reflectionToString(this);
}
}
这是一个简单的JavaBean,描述的是一个员工的基本信息,其中id号是员工编号,按照进入公司的先后顺序编码,position是岗位描述,表示是经理还是普通职员,这是一个枚举类型,代码如下:
enum Position{
Boss, Manager, Staff
}
职位有三个级别:Boss(老板),Manager(经理),Staff(普通职员)。
注意Employee类中的compareTo方法,它是Comparable接口要求必须实现的方法,这里使用apache的工具类来实现,表明是按照id的自然序列排序的(也就是升序)。一切准备完毕,我们看看如何排序:
public static void main(Stringargs){
List<Employee>list=new ArrayList<Employee>(5);
//一个老板
list.add(new Employee(1001,/"张三/",Position.Boss));
//两个经理
list.add(new Employee(1006,/"赵七/",Position.Manager));
list.add(new Employee(1003,/"王五/",Position.Manager));
//两个职员
list.add(new Employee(1002,/"李四/",Position.Staff));
list.add(new Employee(1005,/"马六/",Position.Staff));
//按照id排序,也就是按照资历深浅排序
Collections.sort(list);
for(Employee e:list){
System.out.println(e);
}
}
在收集数据时按照职位高低来收集,这也是“为领导服务”理念的体现嘛,先登记领导,然后是小领导,最后是普通员工。排序后的输出如下:
[email protected][id=1001,name=张三,position=Boss]
[email protected][id=1002,name=李四,position=Staff]
[email protected][id=1003,name=王五,position=Manager]
[email protected][id=1005,name=马六,position=Staff]
[email protected][id=1006,name=赵七,position=Manager]
是按照id号升序排列的,结果正确,但是,有时候我们希望按照职位来排序,那怎么做呢?此时,重构Employee类已经不合适了,Employee已经是一个稳定类,为了一个排序功能修改它不是一个好办法,那有什么更好的解决办法吗?
有办法,看Collections.sort方法,它有一个重载方法Collections.sort(List<T>list, Comparator<?super T>c),可以接受一个Comparator实现类,这下就好办了,代码如下:
class PositionComparator implements Comparator<Employee>{
@Override
public int compare(Employee o1,Employee o2){
//按照职位降序排列
return o1.getPosition().compareTo(o2.getPosition());
}
}
创建了一个职位排序法,依据职位的高低进行降序排列,然后只要把Collections.sort(list)修改为Collections.sort(list, new PositionComparator())即可实现按职位排序的要求。
现在问题又来了:按职位临时倒序排列呢?注意只是临时的,是否要重写一个排序器?完全不用,有两个解决办法:
直接使用Collections.reverse(List<?>list)方法实现倒序排列。
通过Collections.sort(list, Collections.reverseOrder(new PositionComparator()))也可以实现倒序排列。
第二个问题:先按照职位排序,职位相同再按照工号排序,这如何处理?这可是我们经常遇到的实际问题。很好处理,在compareTo或compare方法中先判断职位是否相等,相等的话再根据工号排序,使用apache的工具类来简化处理,代码如下:
public int compareTo(Employee o){
return new CompareToBuilder()
.append(position, o.position)//职位排序
.append(id, o.id).toComparison();//工号排序
}
在JDK中,对Collections.sort方法的解释是按照自然顺序进行升序排列,这种说法其实是不太准确的,sort方法的排序方式并不是一成不变的升序,也可能是倒序,这依赖于compareTo的返回值,我们知道如果compareTo返回负数,表明当前值比对比值小,零表示相等,正数表明当前值比对比值大,比如我们修改一下Employee的compareTo方法,如下所示:
public int compareTo(Employee o){
return new CompareToBuilder()
.append(o.id, id).toComparison();
}
两个参数调换了一下位置,也就是compareTo的返回值与之前正好相反,再使用Collections.sort进行排序,顺序也就相反了,这样就实现了倒序。
第三个问题:在Java中,为什么要有两个排序接口呢?
很多同学都提出了这个问题,其实也好回答,实现了Comparable接口的类表明自身是可比较的,有了比较才能进行排序;而Comparator接口是一个工具类接口,它的名字(比较器)也已经表明了它的作用:用作比较,它与原有类的逻辑没有关系,只是实现两个类的比较逻辑,从这方面来说,一个类可以有很多的比较器,只要有业务需求就可以产生比较器,有比较器就可以产生N多种排序,而Comparable接口的排序只能说是实现类的默认排序算法,一个类稳定、成熟后其compareTo方法基本不会改变,也就是说一个类只能有一个固定的、由compareTo方法提供的默认排序算法。
注意 Comparable接口可以作为实现类的默认排序法,Comparator接口则是一个类的扩展排序工具。