对List排序一般采用两种方法:
(1)实体类实现Comparable<T>
接口,完成compareTo(T o)
方法。
(2)创建一个Comparator<T>
的实现类,完成compare(T o1, T o2)
方法,然后利用这个比较器对List进行排序。
详情可以看一下我的这篇文章。
当我们书写compareTo(T o)
或compare(T o1, T o2)
方法时,我们知道:当第一个参数小于第二个参数时返回负整数;当第一个参数等于第二个参数时返回0;当第一个参数大于第二个参数时返回正整数。
但一定要记住3个原则:
1. 确保:sgn(compare(x, y)) == -sgn(compare(y, x)).
2. 确保:如果((compare(x, y)>0) && (compare(y, z)>0)),那么compare(x, z)>0.
3. 确保:如果compare(x, y)==0,那么对于任意的z都有sgn(compare(x, z))==sgn(compare(y, z))成立.
sgn(expression)是一个数学正负号函数,根据expression是负数、0、正数分别返回-1、0、1。
可以不实现equals()方法,也不一定要求(compare(x, y)==0) == (x.equals(y))。但建议正确实现各个方法。
由于JDK7之前Collections.sort
和Arrays.sort
所使用的排序算法并没有对此做严格的限制,所以可以在x、y相等的时候不返回0,但JDK7之后采用更高效的TimSort算法进行排序,就需要严格遵循上面的3个原则,否则就会出现下面这样的异常:
Exception in thread “main” java.lang.IllegalArgumentException: Comparison method violates its general contract!
at java.util.TimSort.mergeHi(Unknown Source)
at java.util.TimSort.mergeAt(Unknown Source)
at java.util.TimSort.mergeForceCollapse(Unknown Source)
at java.util.TimSort.sort(Unknown Source)
at java.util.Arrays.sort(Unknown Source)
at java.util.ArrayList.sort(Unknown Source)
at java.util.Collections.sort(Unknown Source)
at cn.test.JDK7.main(JDK7.java:32)
该异常并不是总会出现,只有当集合中元素顺序影响到算法排序时才会出现(如TimSort在Merge过程中出触发了“胜利N次”优化,并且A[base1]==A[base1+x])&&(A[base1+x]==B[base2]时,具体可以看一下源码中mergeLo()和mergeHi()方法的实现),所以在开发环境下很难复现这个异常,通常只会在产品环境下发现。所以为了遵循原则,也是为了养成良好的编程习惯,一定要正确实现比较器。
下面是一个会出现异常的Case:
package cn.test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class JDK7 {
private static final String SORT_TIME_VALUE = "sort_time_value";
private static final String PRICE_DOLLAR = "price_dollar";
private static ArrayList<GameBean> gameBeans = new ArrayList<>();
public static void main(String[] args) {
add("0", 7);
add("3", 1);
add("0", 25);
add("1", 2);
add("0", 2);
add("1", 1);
add("0", 1);
add("1", 1);
add("0", 4);
add("1", 1);
add("0", 2);
add("1", 1);
add("0", 3);
add("2", 1);
add("1", 1);
Collections.sort(gameBeans, new GameComparator(SORT_TIME_VALUE, 1));
for (GameBean gameBean : gameBeans) {
System.out.println(gameBean.getTime());
}
}
private static void add(String time, int count) {
for (int i = 0; i < count; i++) {
GameBean gameBean0 = new GameBean();
gameBean0.setTime(time);
gameBean0.setPrice(time);
gameBeans.add(gameBean0);
}
}
static class GameComparator implements Comparator<GameBean> {
String mSortField;
int mSortType;// 1:升序 -1:降序
public GameComparator(String sortField) {
this(sortField, 1);
}
public GameComparator(String sortField, int sortType) {
this.mSortField = sortField;
this.mSortType = sortType;
}
@Override
public int compare(GameBean lhs, GameBean rhs) {
int ret = 0;
if (SORT_TIME_VALUE.equals(mSortField)) {
Long x = Long.valueOf(lhs.getTime());
Long y = Long.valueOf(rhs.getTime());
ret = mSortType * x.compareTo(y);
} else if (PRICE_DOLLAR.equals(mSortField)) {
Float x = Float.valueOf(lhs.getPrice());
Float y = Float.valueOf(rhs.getPrice());
ret = mSortType * x.compareTo(y);
}
return ret;
// return (Integer.valueOf(lhs.getTime()) > Integer.valueOf(rhs.getTime())) ? 1 : -1;
}
}
static class GameBean {
private String time;
private String price;
public String getTime() {
return time;
}
public void setTime(String time) {
this.time = time;
}
public String getPrice() {
return price;
}
public void setPrice(String price) {
this.price = price;
}
}
}
注意:
- 不要用三目运算符返回. o1 > o2 ? 1 : -1没有考虑相等的情况,如果o1等于o2就违背了3条原则。
- 不要将long强转成int. 如果long超过了int最大值会导致数据错误,进而影响排序。
- 不要忽略比较的传递性. 尤其是比较逻辑很复杂时。
想了解更多,可以看一下下面几篇文章: