首页 » 编写高质量代码:改善Java程序的151个建议 » 编写高质量代码:改善Java程序的151个建议全文在线阅读

《编写高质量代码:改善Java程序的151个建议》建议73:使用Comparator进行排序

关灯直达底部

在项目开发中,我们经常要对一组数据进行排序,或者升序或者降序,在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接口则是一个类的扩展排序工具。