在现实生活中,我们可能会遇到需要对集合内的对象进行排序的场景,比如,有一个游戏得分排行榜,如先按照分数的高低由高到低排序,在分数相同的情况下,按照记录创建的时间由早到新的顺序排序。
在Java语言中,要实现集合内对象的排序,咱们可以采用如下两种方式来完成:
接下来,我们先使用Comparable和Comparator、结合示例来完成集合内对象排序的功能,然后,对这两种方式进行比较;最后,结合多属性排序的话,给出相对较好的实践方法。
编写游戏记录类GameRecord,然后实现Comparable接口#compareTo方法。
public int compareTo(T o);
具体代码如下:
import java.util.Date;
import org.apache.http.client.utils.DateUtils;
/**
* @author wangmengjun
*
*/
public class GameRecord implements Comparable<GameRecord> {
/**玩家名称*/
private String name;
/**记录创建时间*/
private Date createTime;
/**得分*/
private int score;
/**
* @param name
* @param createTime
* @param score
*/
public GameRecord(String name, Date createTime, int score) {
this.name = name;
this.createTime = createTime;
this.score = score;
}
/**
* @return the name
*/
public String getName() {
return name;
}
/**
* @param name the name to set
*/
public void setName(String name) {
this.name = name;
}
/**
* @return the createTime
*/
public Date getCreateTime() {
return createTime;
}
/**
* @param createTime the createTime to set
*/
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
/**
* @return the score
*/
public int getScore() {
return score;
}
/**
* @param score the score to set
*/
public void setScore(int score) {
this.score = score;
}
/* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return "GameRecord [name=" + name + ", createTime="
+ DateUtils.formatDate(createTime, "yyyy-MM-dd HH:mm:ss") + ", score=" + score
+ "]";
}
/* (non-Javadoc)
* @see java.lang.Comparable#compareTo(java.lang.Object)
*/
@Override
public int compareTo(GameRecord other) {
int scoreComapre = Integer.compare(other.getScore(), score);
/**
* 先按照分数从高到低排列,
* 如果分数一样,按照记录创建的时间排序
*/
if (scoreComapre == 0) {
return createTime.compareTo(other.getCreateTime());
}
return scoreComapre;
}
}
当GameRecord类实现Comparable接口之后,该类对象就具有比较的功能了,然后我们要做的就是对GameRecord对象的集合类进行排序即可,集合的排序可以采用java.util.Collections类的sort方法完成。
代码示例如下:
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import org.apache.commons.httpclient.util.DateParseException;
import org.apache.commons.httpclient.util.DateUtil;
import org.apache.http.client.utils.DateUtils;
/**
* @author wangmengjun
*
*/
public class Main {
/**
* 可用于转换的日期格式集合
*/
private static final Collection<String> AVAILABLE_DATE_FORMATTERS = Arrays
.asList("yyyy-MM-dd HH:mm:ss");
public static void main(String[] args) throws DateParseException {
Date d1 = DateUtil.parseDate("2015-12-31 00:22:30", AVAILABLE_DATE_FORMATTERS);
Date d2 = DateUtil.parseDate("2016-05-11 12:56:01", AVAILABLE_DATE_FORMATTERS);
Date d3 = DateUtil.parseDate("2016-05-11 12:56:01", AVAILABLE_DATE_FORMATTERS);
GameRecord r1 = new GameRecord("Wang", d1, 300);
GameRecord r2 = new GameRecord("Meng", d2, 100);
GameRecord r3 = new GameRecord("Jun", d3, 300);
List<GameRecord> records = Arrays.asList(r3, r2, r1);
System.out.println("排序前的结果==>");
//System.out.println(records);
printGameRecordInfo(records);
Collections.sort(records);
System.out.println("排序后的结果==>");
printGameRecordInfo(records);
}
private static void printGameRecordInfo(Collection<GameRecord> records) {
for (GameRecord record : records) {
System.out.println(String.format("%s\t%d\t%s",
DateUtils.formatDate(record.getCreateTime(), "yyyy-MM-dd HH:mm:ss"),
record.getScore(), record.getName()));
}
}
}
运行结果如下:
排序前的结果==>
2016-05-11 12:56:01 300 Jun
2016-05-11 12:56:01 100 Meng
2015-12-31 00:22:30 300 Wang
排序后的结果==>
2015-12-31 00:22:30 300 Wang
2016-05-11 12:56:01 300 Jun
2016-05-11 12:56:01 100 Meng
如果采用Comparator来实现,那么实体类GameRecord类无需实现Comparable接口,无需改变实体类的内容。
我们要做的就是建立一个外部的Comparator即可。实体类GameRecord和外部Compartor代码如下:
import java.util.Date;
import org.apache.http.client.utils.DateUtils;
/**
* @author wangmengjun
*
*/
public class GameRecord {
/**玩家名称*/
private String name;
/**记录创建时间*/
private Date createTime;
/**得分*/
private int score;
/**
* @param name
* @param createTime
* @param score
*/
public GameRecord(String name, Date createTime, int score) {
this.name = name;
this.createTime = createTime;
this.score = score;
}
/**
* @return the name
*/
public String getName() {
return name;
}
/**
* @param name the name to set
*/
public void setName(String name) {
this.name = name;
}
/**
* @return the createTime
*/
public Date getCreateTime() {
return createTime;
}
/**
* @param createTime the createTime to set
*/
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
/**
* @return the score
*/
public int getScore() {
return score;
}
/**
* @param score the score to set
*/
public void setScore(int score) {
this.score = score;
}
/* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return "GameRecord [name=" + name + ", createTime="
+ DateUtils.formatDate(createTime, "yyyy-MM-dd HH:mm:ss") + ", score=" + score
+ "]";
}
}
比较器GameRecordComparator实现Comparator接口,然后实现Comparator的compare方法:
比较分数(从高到低),如果分数一致,那么比较记录创建时间 (从早到新)
public int compare(GameRecord r1, GameRecord r2) {
int scoreCompare = Integer.compare(r2.getScore(), r1.getScore());
return scoreCompare == 0 ? r1.getCreateTime().compareTo(r2.getCreateTime()) : scoreCompare;
}
GameRecordComparator类的详细代码如下:
import java.util.Comparator;
/**
* @author wangmengjun
*
*/
public class GameRecordComparator implements Comparator<GameRecord> {
/* (non-Javadoc)
* @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
*/
@Override
public int compare(GameRecord r1, GameRecord r2) {
int scoreCompare = Integer.compare(r2.getScore(), r1.getScore());
return scoreCompare == 0 ? r1.getCreateTime().compareTo(r2.getCreateTime()) : scoreCompare;
}
}
然后,我们就可以直接调用java.util.Collections类的sort方法完成排序,java.util.Collections类的sort方法源码如下:
public static <T> void sort(List<T> list, Comparator<? super T> c) {
Object[] a = list.toArray();
Arrays.sort(a, (Comparator)c);
ListIterator i = list.listIterator();
for (int j=0; j<a.length; j++) {
i.next();
i.set(a[j]);
}
}
测试代码如下:
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import org.apache.commons.httpclient.util.DateParseException;
import org.apache.commons.httpclient.util.DateUtil;
import org.apache.http.client.utils.DateUtils;
/**
* @author wangmengjun
*
*/
public class Main {
/**
* 可用于转换的日期格式集合
*/
private static final Collection<String> AVAILABLE_DATE_FORMATTERS = Arrays
.asList("yyyy-MM-dd HH:mm:ss");
public static void main(String[] args) throws DateParseException {
Date d1 = DateUtil.parseDate("2015-12-31 00:22:30", AVAILABLE_DATE_FORMATTERS);
Date d2 = DateUtil.parseDate("2016-05-11 12:56:01", AVAILABLE_DATE_FORMATTERS);
Date d3 = DateUtil.parseDate("2016-05-11 12:56:01", AVAILABLE_DATE_FORMATTERS);
GameRecord r1 = new GameRecord("Wang", d1, 300);
GameRecord r2 = new GameRecord("Meng", d2, 100);
GameRecord r3 = new GameRecord("Jun", d3, 300);
List<GameRecord> records = Arrays.asList(r3, r2, r1);
System.out.println("排序前的结果==>");
//System.out.println(records);
printGameRecordInfo(records);
Collections.sort(records, new GameRecordComparator());
System.out.println("排序后的结果==>");
printGameRecordInfo(records);
}
private static void printGameRecordInfo(Collection<GameRecord> records) {
for (GameRecord record : records) {
System.out.println(String.format("%s\t%d\t%s",
DateUtils.formatDate(record.getCreateTime(), "yyyy-MM-dd HH:mm:ss"),
record.getScore(), record.getName()));
}
}
}
运行结果如下:
排序前的结果==>
2016-05-11 12:56:01 300 Jun
2016-05-11 12:56:01 100 Meng
2015-12-31 00:22:30 300 Wang
排序后的结果==>
2015-12-31 00:22:30 300 Wang
2016-05-11 12:56:01 300 Jun
2016-05-11 12:56:01 100 Meng
通过上面的代码,我们完成了使用Comparable以及Comparator实现对象集合排序的示例,接下来,我们来简单分析一下Comparable和Comparator的区别。
比如:
import java.util.Comparator;
/**
* @author wangmengjun
*
*/
public class ScoreComparator implements Comparator<GameRecord> {
/* (non-Javadoc)
* @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
*/
@Override
public int compare(GameRecord r1, GameRecord r2) {
return Integer.compare(r2.getScore(), r1.getScore());
}
}
Collections.sort(records, new ScoreComparator());
排序前的结果==>
2016-05-11 12:56:01 300 Jun
2016-05-11 12:56:01 100 Meng
2015-12-31 00:22:30 300 Wang
排序后的结果==>
2016-05-11 12:56:01 300 Jun
2015-12-31 00:22:30 300 Wang
2016-05-11 12:56:01 100 Meng
3.2 游戏记录创建时间比较器
import java.util.Comparator;
/**
* @author wangmengjun
*
*/
public class CreateTimeComparator implements Comparator<GameRecord> {
/* (non-Javadoc)
* @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
*/
@Override
public int compare(GameRecord r1, GameRecord r2) {
return r1.getCreateTime().compareTo(r2.getCreateTime());
}
}
Collections.sort(records, new CreateTimeComparator());
排序前的结果==>
2016-05-11 12:56:01 300 Jun
2016-05-11 12:56:01 100 Meng
2015-12-31 00:22:30 300 Wang
排序后的结果==>
2015-12-31 00:22:30 300 Wang
2016-05-11 12:56:01 300 Jun
2016-05-11 12:56:01 100 Meng
如果不想改变原来类的状态,添加排序功能,可以使用Comparator来做。如果有多种排序策略,可以采用Comparator来做。
在上述示例中,我们采用先按照分数排序(降序),然后如果分数相等,按照记录的创建日期排序(升序)。以GameRecordComparator的compare方法为例:
public int compare(GameRecord r1, GameRecord r2) {
int scoreCompare = Integer.compare(r2.getScore(), r1.getScore());
return scoreCompare == 0 ? r1.getCreateTime().compareTo(r2.getCreateTime()) : scoreCompare;
}
如果属性比较多,假设在分数和记录创建时间之外还需要对名称等字段进行比较,那么compare方法中,我们需要一个个地对各个属性字段逐个比较,这样写的越多,我们的if语句或者三元运算符逻辑就会增多。
有没有更加简便的方式来完成多属性排序呢?答案是肯定的。
下面我们就采用CompareToBuilder以及ComparisonChain来完成。
public int compare(GameRecord r1, GameRecord r2) {
return new CompareToBuilder().append(r2.getScore(), r1.getScore())
.append(r1.getCreateTime(), r2.getCreateTime())
.toComparison();
}
3.6 使用com.google.common.collect.ComparisonChain完成多属性排序
public int compare(GameRecord r1, GameRecord r2) {
return ComparisonChain.start().compare(r2.getScore(), r1.getScore())
.compare(r1.getCreateTime(), r2.getCreateTime()).result();
}