使用jdk1.7后发现的collections.sort(List list)排序问题和浅析
自己的电脑系统重装以后,装了一个最新的jdk(1.7),当然了,本地eclipse的编译等级仍然是1.5。
之后有一个功能是需要调用服务端的一个方法来达到对ArrayList排序的功能,大部分朋友应该都知道java.util.Collections下有一个sort(List
平时做算法的情况比较少,虽然知道可以用Collections来排序List,但自己用的也不多,在编写完客户端代码以后,本地测试完了,发现了问题,好像排序的顺序和自己的预期相反,当时也以为自己可能记错了jdk的api,所以随后自己在compareTo方法的返回中将1和-1进行了调换,好,现在本地的结果是对的,本地是jdk1.7的环境。
本地测试通过以后,在把代码放到服务器上进行测试,这时候发现的情况让我非常惊讶,排序结果竟又和本地不同,花了不少时间,一直以为代码的替换有些问题,在确认不是因为代码替换带来的问题后,检查了一下服务器端的jdk版本,是1.5的,感觉问题可能出在Collections.sort(List
为避免其他可能的问题带来的干扰,我写了一个单独的简单类分别在本地(1.7)和服务器(1.5)环境上进行测试:
import java.util.ArrayList; import java.util.Collections; import java.util.List; public class TempObj implements Comparable{ private int value; public TempObj() { } public TempObj(int value) { this.value = value; } public int getValue() { return value; } public void setValue(int value) { this.value = value; } public int compareTo(TempObj o) { System.out.println(this.getValue()); System.out.println(o.getValue()); return 0; } public static void main(String[] args){ List list = new ArrayList (); list.add(new TempObj(1)); list.add(new TempObj(2)); Collections.sort(list); } }
执行后,发现本地(1.7)和服务器(1.5)在compareTo中打印的结果也是完全相反的,换句话说,toCompare的实现中,对于待比较的两个参数完全相反(由打印出的this.getValue()和o.getValue()看出),如此看来自然会影响我对toCompare的实现返回结果了(1和-1)。
再查看jdk1.7与jdk1.5在该方法上的实现,发现确实有些不同。
关于1.7下Arrays.sort(Object[] a)的实现:
public static void sort(Object[] a) { if (LegacyMergeSort.userRequested) legacyMergeSort(a); else ComparableTimSort.sort(a); } /** To be removed in a future release. */ private static void legacyMergeSort(Object[] a) { Object[] aux = a.clone(); mergeSort(aux, a, 0, a.length, 0); }
而1.5下Arrays.sort(Object[] a)的实现:
public static void sort(Object[] a) { Object[] aux = (Object[])a.clone(); mergeSort(aux, a, 0, a.length, 0); }
我注意到1.7下的sort有一个分支判断,当LegacyMergeSort.userRequested为true的情况下,采用legacyMergeSort,否则采用ComparableTimSort。LegacyMergeSort.userRequested的字面意思大概就是“用户请求传统归并排序”的意思,这个分支调用的是与jdk1.5相同的方法来实现功能。
我没有花时间去研究jdk1.7为什么要这么做,以及在另外一个分支判断中 ComparableTimSort的具体实现是如何的,我只需要尽量解决当前发现的问题,网上搜索过一下后,发现在执行Collections.sort进行排序前可以调用:
System.setProperty("java.util.Arrays.useLegacyMergeSort", "true");
来实现设置LegacyMergeSort.userRequested的赋值,测试过以后确实有效,这样能够在jdk1.7下强制使用老的排序方式达到预期功能。
其他的问题:
1、如果客户生产环境中的某些软件需要升级需要变更(升级)到高版本的jdk,还是可能会遇到不少难以预期的问题,例如像我遇到的排序结果本末倒置,所以升级真是挺危险的一件事。
2、在jdk1.7的legacyMergeSort方法注释中,注意到了:“To be removed in a future release.”,这么说在1.7以后的版本中,老的mergeSort方法可能会被ComparableTimSort替代,这样也就不能通过改变userLegacyMergeSort的值来影响排序的实现了,这可能确实会带来一些问题,如果需要进行自定义逻辑的排序,那么以后也需要留意。
3、我不确定这算不算一个Bug,因为目前也没有仔细了解 ComparableTimSort 的实现,发现问题后目前在网上也没有搜索到对这个问题的任何描述,但是这确实对我的程序有很大的影响,因为在toCompare中比对和被比对参数的调换必然影响到用户的自定义判断;另外我也检查了在jdk1.6下的实现与1.5相同,也就是说,是1.7才开始采用这个方式的,本地的jdk1.7具体版本是jdk-7u3-windows-x64。