利用Comparator进行复杂对象Collection的排序

需求场景描述: 需要对一个Collection进行某种方式的排序。比如一个User对象的集合,我们需要按公司和姓名进行排序。User对象如下: [java]package com.guoweiwei.test.comparator; public class User { private String name; private String sex; private String company; User(){}; User(String name, String sex, String company){ this.name = name; this.sex = sex; this.company = company; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } public void setCompany(String company) { this.company = company; } public String getCompany() { return company; } }[/java] 解决方案: 一般简单的排序,我们用现有的排序算法就可以解决了。但是对于复杂的排序,比如根据公司名称、职位、工号等信息排序的话,那往往是不够的。这就需要我们实现Comparator接口,通过该接口进行比较。Comparator接口只定义了一个方法compare,该方法根据一定的策略比较两个对象,然后返回一个整数值。如下为该接口的定义: [java]int compare(T o1, T o2);[/java] 如果o1小于o2,返回-1;o1等于o2,返回0;o1大于o2,返回1。 根据这个原理,我们实现如下的Comparator接口代码: [java]package com.guoweiwei.test.comparator; import java.util.Comparator; public class UserComparator implements Comparator { @Override public int compare(User user1, User user2){ int comparison = -1; String company1 = user1.getCompany(); String company2 = user2.getCompany(); String name1 = user1.getName(); String name2 = user2.getName(); if(company1.length() != 0 && company2.length() != 0){ comparison = company1.compareTo(company2); } /在公司名称相同的前提下,根据姓名比较/ if(comparison ==0 && name1.length() != 0 && name2.length() != 0){ comparison = name1.compareTo(name2); } return comparison; } }[/java] 有了这个比较器,我们就可以根据这个比较器对User列表进行排序,返回值是根据公司名称和用户姓名进行排序的Array或List对象。看一下我们的比较代码: [java]@SuppressWarnings(“unchecked”) public static void startCompare(){ User user1 = new User(“A”,”男”,”AA”); User user2 = new User(“A”,”男”,”BB”); User user3 = new User(“B”,”女”,”AA”); User user4 = new User(“B”,”男”,”BB”); User user5 = new User(“C”,”男”,”BB”); User[] users = new User[]{user1,user2,user3,user4,user5}; Comparator userComparator = new UserComparator(); Arrays.sort(users,userComparator); for(User user : users){ System.out.println(user.getCompany() + “ “ + user.getName() + “ “ + user.getSex()); } }[/java] 运行程序,我们将得到如下的结果: [java]AA A 男 AA B 女 BB A 男 BB B 男 BB C 男 [/java] 有没有更简单的实现方式呢?回答是肯定的,那就是用ComparatorChain。ComparatorChain把若干个Comparator对象连起来,组成一个Comparator链。在比较两个对象的时候,依次用这个Comparator链中的每个Comparator进行比较,直到有一个Comparator返回非零值则结束比较;如果相等,则继续用该链中的下一个Comparator比较。如下代码所示: [java]@SuppressWarnings(“unchecked”) public static void start(){ User user1 = new User(“A”,”男”,”AA”); User user2 = new User(“A”,”男”,”BB”); User user3 = new User(“B”,”女”,”AA”); User user4 = new User(“B”,”男”,”BB”); User user5 = new User(“C”,”男”,”BB”); User[] users = new User[]{user1,user2,user3,user4,user5}; ComparatorChain comparatorChain = new ComparatorChain(); comparatorChain.addComparator(new BeanComparator(“name”)); comparatorChain.addComparator(new BeanComparator(“sex”),true); comparatorChain.addComparator(new BeanComparator(“company”)); Arrays.sort(users,comparatorChain); for(User user : users){ System.out.println(user.getName() + “ “ + user.getSex() + “ “ + user.getCompany()); } }[/java] 运行程序,我们将得到如下的结果: [java]A 男 AA A 男 BB B 男 BB B 女 AA C 男 BB[/java] 这里要说明的两点是:①ComparatorChain来自commons.collections这个包,BeanComparator 来自commons.beanutils这个包,所以一定要引用这两个包才行。②对于comparatorChain.addComparator()方法中的第二个参数,其含义是是否反转(也就是为true就倒序,默认是升序)。 需求升级: 更进一步,大家或许有疑问了。这B公司的AA和BB好像不对啊,这个中文是依据什么排序的呢?按性别倒过来应该是女、男才对啊。到这一步,我也说不上来,因为我不懂这里的中文是根据什么来排序的。那面对中文该怎么办呢? 还好,有很多中文工具可以帮我们实现,比较常见的有Collator和pinyin4j。Collator是JDK自带的一个本地化工具,pinyin4j是google上的一个开源项目。以Collator实现的中文排序Comparator如下: [java]public class ChineseComparator implements Comparator { private final static Collator collator = Collator.getInstance(Locale.CHINESE); @Override public int compare(String str1, String str2){ int comparison = -1; if(str1 == null && str2 == null){ comparison = 0; }elseif(str1 == null){ comparison = -1; }elseif(str2 == null){ comparison = 1; }else{ CollationKey key1 = collator.getCollationKey(str1); CollationKey key2 = collator.getCollationKey(str2); comparison = key1.compareTo(key2); } return comparison; } }[/java] 将需要排序的User数组和Comparator装饰改为如下: [java]@SuppressWarnings(“unchecked”) public static void start(){ User user1 = new User(“马加爵”,”男”,”招商局”); User user2 = new User(“李刚”,”男”,”公安局”); User user3 = new User(“马加爵”,”女”,”招商局”); User user4 = new User(“李英”,”男”,”公安局”); User user5 = new User(“马加爵”,”男”,”公安局”); User[] users = new User[]{user1,user2,user3,user4,user5}; Comparator chineseComparator = new ChineseComparator(); ComparatorChain comparatorChain = new ComparatorChain(); comparatorChain.addComparator(new BeanComparator(“company”,chineseComparator)); comparatorChain.addComparator(new BeanComparator(“name”,chineseComparator)); comparatorChain.addComparator(new BeanComparator(“sex”,chineseComparator)); Arrays.sort(users,comparatorChain); for(User user : users){ System.out.println(user.getCompany() + “ “ + user.getName() + “ “ + user.getSex()); } } }[/java] 运行代码,得到如下结果: [java]公安局 李刚 男 公安局 李英 男 公安局 马加爵 男 招商局 马加爵 男 招商局 马加爵 女 [/java] 延伸阅读: [java]对null的比较: Comparator userComparator = new UserComparator(); Comparator nullComparator = new NullComparator(userComparator,true); 固定顺序的比较: String[] weeks = {“星期一”,”星期二”,”星期三”,”星期四”,”星期五”,”星期六”,”星期日”}; Comparator fixedComparator = new FixedOrderComparator(weeks); Comparator weeksComparator = new BeanComparator(“week”,fixedComparator); [/java]