
本文共 17727 字,大约阅读时间需要 59 分钟。
java对象排序(使用Comparator)
对象属性为int类型排序
首先我们假设有这么一个model类:
public class JobCandidate { private String name; private String sex; private int age; public JobCandidate(String name, String sex, int age) { this.name = name; this.sex = sex; this.age = age; } /*@Override public int compareTo(JobCandidate candidate) { return (this.getAge() < candidate.getAge() ? -1 : this.getAge() == candidate.getAge() ? 0 :1); }*/ public static ComparatorageComparator = new Comparator () { @Override public int compare(JobCandidate jc1, JobCandidate jc2) { return (jc2.getAge() < jc1.getAge() ? -1 : (jc2.getAge() == jc1.getAge() ? 0 : 1)); } }; public static Comparator nameComparator = new Comparator () { @Override public int compare(JobCandidate jc1, JobCandidate jc2) { return (jc1.getName().compareTo(jc2.getName())); } }; public static Comparator nameChina = new Comparator () { Collator cmp = Collator.getInstance(java.util.Locale.CHINA); @Override public int compare(JobCandidate o1, JobCandidate o2) { CollationKey c1 = cmp.getCollationKey(o1.getName()); CollationKey c2 = cmp.getCollationKey(o2.getName()); return cmp.compare(c1.getSourceString(), c2.getSourceString());// return cmp.compare(o1.getName(), o2.getName());// return (o1.getName().compareTo(o2.getName())); } }; @Override public String toString() { return " Name: " + this.name + ", sex: " + this.sex + ", age:" + this.age; } 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 int getAge() { return age; } public void setAge(int age) { this.age = age; }}
我们先看ageComparator
上面的类中声明了一个ageComparator静态变量,其写了一个匿名类实现了compare()的方法。 要是不想使用匿名类的方式,可以在外部再创建一个类来实现Comparator接口。 接着我再写一个排序类。也就是你想通过JobCandidate类中那个属性来排序。JobCandidate类里定义了排序方法。
public class JobCandidateSorter { ArrayListjobCandidateList = new ArrayList (); public JobCandidateSorter(ArrayList jobCandidateList) { this.jobCandidateList = jobCandidateList; } //使用Comparable类中compareTo方法来做 /*public ArrayList getSortedJobCandidateByAge() { Collections.sort(jobCandidate); return jobCandidate; }*/ public ArrayList getSortedJobCandidateByAge(){ Collections.sort(jobCandidateList, JobCandidate.ageComparator); return jobCandidateList; } public ArrayList getSortedJobCandidateByName(){ Collections.sort(jobCandidateList, JobCandidate.nameComparator); return jobCandidateList; } public ArrayList getSortedJobCandidateByComparator() { Collections.sort(jobCandidateList, JobCandidate.nameChina); return jobCandidateList; }}
同样目前我们只看getSortedJobCandidateByAge()
的方法:
Collections.sort(jobCandidateList, JobCandidate.ageComparator);
该方法就是调用Collections.sort()
方法。
jobCandidateList
的是List<jobCandidateList>
类型的。 第二个参数:JobCandidate.ageComparator
就是我在JobCandidate
类中定义 的匿名类。该匿名类重写了compare()
的方法。 @Overridepublic int compare(JobCandidate jc1, JobCandidate jc2) { //jc1>jc2说明是 return (jc2.getAge() < jc1.getAge() ? -1 : (jc2.getAge() == jc1.getAge() ? 0 : 1));}
好了我们现在写个测试类,看看效果:
public class JobCandidateSorterTest { public void testGetSortedJobCandidateByAge() throws Exception { JobCandidate jobCandidate1 = new JobCandidate("王林", "Male", 26); JobCandidate jobCandidate2 = new JobCandidate("杨宝", "Female", 23); JobCandidate jobCandidate3 = new JobCandidate("李镇", "Female", 20); JobCandidate jobCandidate4 = new JobCandidate("刘迪", "Male", 24); ArrayListjobCandidateList = new ArrayList (); jobCandidateList.add(jobCandidate1); jobCandidateList.add(jobCandidate2); jobCandidateList.add(jobCandidate3); jobCandidateList.add(jobCandidate4); JobCandidateSorter jobCandidateSorter = new JobCandidateSorter(jobCandidateList); ArrayList sortedJobCandidate = jobCandidateSorter.getSortedJobCandidateByAge();//调用排序方法 System.out.println("-----Sorted JobCandidate by age: Ascending-----"); for (JobCandidate jobCandidate : sortedJobCandidate) { System.out.println(jobCandidate); } jobCandidateSorter.getSortedJobCandidateByComparator();//Collator.getInstance(java.util.Locale.CHINA) System.out.println(jobCandidateList); } public static void main(String[] args) throws Exception { JobCandidateSorterTest jt = new JobCandidateSorterTest(); jt.testGetSortedJobCandidateByAge(); }}
打印结果
—–排序后的结果—–
Name: 王林, sex: Male, age:26 Name: 刘迪, sex: Male, age:24 Name: 杨宝, sex: Female, age:23 Name: 李镇, sex: Female, age:20 ======未排序==================== [ Name: 李镇, sex: Female, age:20, Name: 刘迪, sex: Male, age:24, Name: 王林, sex: Male, age:26, Name: 杨宝, sex: Female, age:23]
对象属性为String的排序
我们接下来看到上面JobCandidate
类代码中静态变量nameComparator
。
public static ComparatornameComparator = new Comparator () { @Override public int compare(JobCandidate jc1, JobCandidate jc2) { return (jc1.getName().compareTo(jc2.getName())); } };
其中Comparable
是带有单一 compareTo()
方法的接口.也就是说要重写conpareTo()
comparable
类。不重写的话,就是采用默认的。 以上就是通过对象属性排序结果,这种排序方式只适合英文排序结果,由于age
是数字,结果中英文都一样。
我们看看name
的排序结果:
—–排序后的结果—–
Name: 刘迪, sex: Male, age:24 Name: 李镇, sex: Female, age:20 Name: 杨宝, sex: Female, age:23 Name: 王林, sex: Male, age:26
这个结果是默认中文字符串排序的顺序,他们拼音首字母:L L Y W
,
L L Y M
。 对象属性为String的中文排序。
我们接下来看到上面JobCandidate类代码中静态变量nameChina
。
public static ComparatornameChina = new Comparator () { Collator cmp = Collator.getInstance(java.util.Locale.CHINA); @Override public int compare(JobCandidate o1, JobCandidate o2) {// CollationKey c1 = cmp.getCollationKey(o1.getName());// CollationKey c2 = cmp.getCollationKey(o2.getName());// return cmp.compare(c1.getSourceString(), c2.getSourceString()); return cmp.compare(o1.getName(), o2.getName()); } };
—–排序后的结果—–
Name: 李镇, sex: Female, age:20 Name: 刘迪, sex: Male, age:24 Name: 王林, sex: Male, age:26 Name: 杨宝, sex: Female, age:23
拼音首字母排序:L L W Y
这才是我们想要的。
Collator
类。在网上我找到这么一句话: 默认情况下,字符串的比较使用字符串包含字符的ASCII码来比较。 java.text.Collator使用字符串包含字符在指定语言的自然顺序(譬如中文汉字的自然顺序)做比较。是locale敏感的。 如果只比较纯英文,那么就不需要使用collator。一般情况下我们直接使用Collections.sort()来执行排序操作、或者根据字符的ASCII码来排序。但是,如果其中包含一些特殊字符如é、中文汉字,那边使用collator和默认的collections.sort排序结果并不一致。
也就是说我们要使用中文排序的话,就需要使用Collator
类,再调Collator
类中的比较方法:compare()
其中compare()
方法是Comparator
接口的单一的比较方法。
上面代码注释掉的:
CollationKey c1 = cmp.getCollationKey(o1.getName()); CollationKey c2 = cmp.getCollationKey(o2.getName()); return cmp.compare(c1.getSourceString(), c2.getSourceString());
这是另一种写法而已,依然使用的是Collator
类。
java对象排序(使用Comparable)
上面使用的是实现Comparator
接口并重写compare()
的方法。
Comparable
的方法并重写compareTo()
的方法来实现排序。 JobCandidate
中代码:
public class JobCandidate implements Comparable{ private String name; private String sex; private int age; public JobCandidate(String name, String sex, int age) { this.name = name; this.sex = sex; this.age = age; } @Override public int compareTo(JobCandidate candidate) { return (this.getAge() < candidate.getAge() ? -1 : this.getAge() == candidate.getAge() ? 0 :1); } @Override public String toString() { return " Name: " + this.name + ", sex: " + this.sex + ", age:" + this.age; } 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 int getAge() { return age; } public void setAge(int age) { this.age = age; }}
JobCandidateSorter
类的代码:
public class JobCandidateSorter { ArrayListjobCandidateList = new ArrayList (); public JobCandidateSorter(ArrayList jobCandidateList) { this.jobCandidateList = jobCandidateList; } //使用Comparable类中compareTo方法来做 public ArrayList getSortedJobCandidateByAge() { Collections.sort(jobCandidateList); return jobCandidateList; }}
测试类JobCandidateSorterTest
的代码:
public class JobCandidateSorterTest { public void testGetSortedJobCandidateByAge() throws Exception { JobCandidate jobCandidate1 = new JobCandidate("王林", "Male", 26); JobCandidate jobCandidate2 = new JobCandidate("杨宝", "Female", 23); JobCandidate jobCandidate3 = new JobCandidate("李镇", "Female", 20); JobCandidate jobCandidate4 = new JobCandidate("刘迪", "Male", 24); ArrayListjobCandidateList = new ArrayList (); jobCandidateList.add(jobCandidate1); jobCandidateList.add(jobCandidate2); jobCandidateList.add(jobCandidate3); jobCandidateList.add(jobCandidate4); JobCandidateSorter jobCandidateSorter = new JobCandidateSorter(jobCandidateList); ArrayList sortedJobCandidate = jobCandidateSorter.getSortedJobCandidateByAge(); System.out.println("-----排序后的结果-----"); for (JobCandidate jobCandidate : sortedJobCandidate) { System.out.println(jobCandidate); } } public static void main(String[] args) throws Exception { JobCandidateSorterTest jt = new JobCandidateSorterTest(); jt.testGetSortedJobCandidateByAge(); }}
排序结果(age字段排序):
—–排序后的结果—–
Name: 李镇, sex: Female, age:20 Name: 杨宝, sex: Female, age:23 Name: 刘迪, sex: Male, age:24 Name: 王林, sex: Male, age:26
这里我们会发现小区别:
compare()
的方法是传入两个参数,也就是两个相同类型的不同对象。 compareTo()
的方法是传入一个参数,和调用该方法的对象是相同类型的。 我重写的compareTo()
:
@Overridepublic int compareTo(JobCandidate candidate) { return (this.getAge() < candidate.getAge() ? -1 : this.getAge() == candidate.getAge() ? 0 :1); }
如果也想进行中文排序,依然是使用:
Collator cmp = Collator.getInstance(java.util.Locale.CHINA);
再调用cmp.compare(o1,o2)
的方法来做。
Collections.sort()
Collections.sort()这个排序的关键方法。不管你实现Comparable
接口还是实现Comparator
接口,都得用到该方法。
Comparable
的接口,这个调用的是: Collections.sort(List list)
源码:
public static> void sort(List list) { Object[] a = list.toArray(); Arrays.sort(a); ListIterator i = list.listIterator(); for (int j=0; j
此时直接调用了Arrays.sort(a)来排序后,将数组的数据写回到list,另外根据方法的定义, 泛型T要求传入的类必须是Comparable类的子类或实现类(我的例子中就是JobCandidate
类), 所以要调用Collections.sort(list)这个方法,传入的list中包含的每行数据必须是implements Comparable这个接口的,否则编译时就会报错。
假设我们实现Comparator
的接口,则调用的是:
以下转载内容
Collections.sort(List list, Comparator<? super T> c)
源码:
public staticvoid sort(List list, Comparator c) { Object[] a = list.toArray(); Arrays.sort(a, (Comparator)c); ListIterator i = list.listIterator(); for (int j=0; j
也是和第一个方法类似,就是调用了Arrays.sort相应的重载方法,看来都是在Arrays里面是实现的,那么就进一步向下看:
public static void sort(Object[] a) { Object[] aux = (Object[])a.clone(); mergeSort(aux, a, 0, a.length, 0); }
看来代码片段交给了mergeSort来处理,而对数组做了一次克隆,作为排序的基础数据,而原来的数组作为排序的目标,mergeSort的代码片段应该是核心部分,我们先放在这里,先看下sort的另一个重载方法,另外需要注意,这里并没有像Collections.sort(Listlist)那样在编译时检查类型,也就是在使用这个方法的时候,数组里面的每行并没有implements Comparable也会不会出错,只是在运行时会报错而已,在下面的源码中会有说明。
Arrays.sort(T[]t, Comparator c)
源码:
public staticvoid sort(T[] a, Comparator c) { aux = (T[])a.clone(); if (c==null) mergeSort(aux, a, 0, a.length, 0); else mergeSort(aux, a, 0, a.length, 0, c); }
看来mergeSort也进行了重载,也就是当传入了自定义的Comparator和不传入自定义的Comparator是调用不同的方法来实现的,然后我们来看下两个方法的实现。
mergeSort(Object[]src , Object[]dst , int low , int high , int off)
源码:
private static void mergeSort(Object[] src, Object[] dest, int low, int high, int off) { int length = high - low; if (length < INSERTIONSORT_THRESHOLD) { for (int i=low; ilow && ((Comparable) dest[j-1]).compareTo(dest[j])>0; j--) swap(dest, j, j-1); return; } int destLow = low; int destHigh = high; low += off; high += off; int mid = (low + high) >>> 1; mergeSort(dest, src, low, mid, -off); mergeSort(dest, src, mid, high, -off); if (((Comparable)src[mid-1]).compareTo(src[mid]) <= 0) { System.arraycopy(src, low, dest, destLow, length); return; } for(int i = destLow, p = low, q = mid; i < destHigh; i++) { if (q >= high || p < mid && ((Comparable)src[p]).compareTo(src[q])<=0) dest[i] = src[p++]; else dest[i] = src[q++]; } } /** * Swaps x[a] with x[b]. */ private static void swap(Object[] x, int a, int b) { Object t = x[a]; x[a] = x[b]; x[b] = t; }
仔细阅读代码可以发现排序是分段递归回调的方式来排序(注意中间的low和high两个参数的变化),每次如果分段的大小大于INSERTIONSORT_THRESHOLD(定义为7)的时候,则再分段,前一段和后一段,然后分开的两段再调用递推,递推后再回归排序,若发现中间分隔的位置两个数据是有序,则认为两段是完全有序的,若不是,那么再将两段做一次排序,此时排序就很好排序了,因为两个块是排序排好的,所以不需要两次循环,只需要循环扫描下去,两个数组按照顺序向下走,分别对比出最小值写入数组,较大者暂时不写入数组与另一个数组的下一个值进行对比,最后一截数据(源码中是通过越界来判定的)写入到尾巴当中:
for(int i = destLow, p = low, q = mid; i < destHigh; i++) { if (q >= high || p < mid && ((Comparable)src[p]).compareTo(src[q])<=0) dest[i] = src[p++]; else dest[i] = src[q++]; }
这段对两个有序数组的排序是很经典的写法,主要是if语句的浓缩,不然代码会写得很长。
注意:这里的代码排序中使用了强制类型转换为Comparable来调用内部的comareTo方法,所以如果你的类没有implements Comparable那么在Collections.sort(Listlist)时编译时会报错上面已经说到,在调用Arrays.sort(Object []t)时,编译时并不会报错,但是运行时会报错为:java.lang.ClassCastExceptionXXXDO cannot be cast to java.lang.Comparable
排序部分我们再看看其重载的mergeSort方法,就是传入了自定义的Comparator的方法
源码片段:mergeSort(Object[]src,Object[]dst,int low,int high,intoff,Comparator c)
private static void mergeSort(Object[] src, Object[] dest, int low, int high, int off, Comparator c) { int length = high - low; if (length < INSERTIONSORT_THRESHOLD) { for (int i=low; ilow && c.compare(dest[j-1], dest[j])>0; j--) swap(dest, j, j-1); return; } int destLow = low; int destHigh = high; low += off; high += off; int mid = (low + high) >>> 1; mergeSort(dest, src, low, mid, -off, c); mergeSort(dest, src, mid, high, -off, c); if (c.compare(src[mid-1], src[mid]) <= 0) { System.arraycopy(src, low, dest, destLow, length); return; } for(int i = destLow, p = low, q = mid; i < destHigh; i++) { if (q >= high || p < mid && c.compare(src[p], src[q]) <= 0) dest[i] = src[p++]; else dest[i] = src[q++]; } }
可以发现算法和上一个方法完全一样,唯一的区别就是排序时使用的compare变成了传入的comparator了,其余的没有任何区别。
大概清楚了,此时发现java提供的排序还是比较高效的,大多数情况下你不需要自己去写排序算法,最后我们再看看TreeSet中的在add的时候如何实现排序的,也是分别传入了comparator和没有传入,我们跟着源码里面,可以看到传入了comparator将这个属性设置给了TreeSet里面定义的一个TreeeMap,而TreeMap中的一个属性设置了这个Comparator:
源码片段:TreeSet以及TreeMap设置Comparator的构造方法
public TreeSet(Comparator comparator) { this(new TreeMap(comparator)); } TreeSet(NavigableMap m) { this.m = m; } public TreeMap(Comparator comparator) { this.comparator = comparator; }
当然没有传入这个Comparator的时候自然没有设置到TreeMap中了,那么我们来看看TreeMap的add方法:
源码片段:TreeSet#add(E e)
public boolean add(E e) { return m.put(e,PRESENT)==null;}
这个m是什么呢?其实通过源码片段7就可以看出,m是开始实例化的一个TreeMap,那么我们就需要看TreeMap的put方法
源码片段:TreeMap#put(K key , V value)
public V put(K key, V value) { Entryt = root; if (t == null) { root = new Entry (key, value, null); size = 1; modCount++; return null; } int cmp; Entry parent; // split comparator and comparable paths Comparator cpr = comparator; if (cpr != null) { do { parent = t; cmp = cpr.compare(key, t.key); if (cmp < 0) t = t.left; else if (cmp > 0) t = t.right; else return t.setValue(value); } while (t != null); } else { if (key == null) throw new NullPointerException(); Comparable k = (Comparable ) key; do { parent = t; cmp = k.compareTo(t.key); if (cmp < 0) t = t.left; else if (cmp > 0) t = t.right; else return t.setValue(value); } while (t != null); } Entry e = new Entry (key, value, parent); if (cmp < 0) parent.left = e; else parent.right = e; fixAfterInsertion(e); size++; modCount++; return null; }
这里判定了是否存在Comparator进行不同方式来写入不同的位置,并没有重载方法,所以实现上也不一定有什么绝对非要如何做,只需要保证代码可读性很好就好,一切为它服务,否则那些过多的设计是属于过度设计,当然并不是说代码设计不重要,但是这些需要适可而止;另外TreeSet里面对于其他的方法也会做排序处理,我们这里仅仅是用add方法来做一个例子而已。
相信你对java的排序有了一些了解,也许本文说了一堆废话,因为本文不是在说排序算法,我们只是告诉你java是如何排序的,你在大部分情况下无需自己写排序算法来完成排序导致一些不必要的bug,而且效率未必有java本身提供的排序算法高效。
参考地址:
发表评论
最新留言
关于作者
