文章目录
前言
在学习中,我们经常会使用List集合及Array数组来操作一定数量的元素对象,而Collections集合工具类包含了一些静态方法来进行集合的排序、交换、查找等操作。本文主要讲述Comparable和Comparator两种实现方式,因此只就排序而言,大多基本数据类型源码中都实现了Comparable接口,重写了compareTo方法,对于自定义类型对象,如果不实现Comparable或Comparator接口,那么无法进行排序。
String实现Compareable接口,重写compareTo方法如下:
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence
public int compareTo(String anotherString) {
int len1 = value.length;
int len2 = anotherString.value.length;
int lim = Math.min(len1, len2);
char v1[] = value;
char v2[] = anotherString.value;
int k = 0;
//此处比较字符串的每一个字符,相等则比较下一个,不相等则返回。
while (k < lim) {
char c1 = v1[k];
char c2 = v2[k];
if (c1 != c2) {
return c1 - c2;
}
k++;
}
return len1 - len2;
}
一、Comparable
1.概述
Comparable的译为可被排序的,代表本身支持排序功能,是一种内部实现的方式,也叫做自然排序。基本数据类型都实现了这个接口,可以进行排序,自定义类实现了此接口,该自定义类会自动拥有可排序能力。一般是在自定义类的时候实现排序,并且规定排序的规则,每次修改排序规则都要修改源码,一般规定好了之后不建议在使用该方式修改源码来更改排序规则。如果需要修改排序规则,可以通过实现Comparator的方式来外部实现某种特定的排序方式。
2.Comparable实例
Student类:
package Test;
/**
* @author Fox
* @date 2021/11/26 14:30
*/
public class Student implements Comparable<Student>{
private int num;//学号
private String name;//姓名
private int age;//年龄
private int socre;//成绩
public Student() {
}
public Student(int num, String name, int age,int socre) {
this.num = num;
this.name = name;
this.age = age;
this.socre = socre;
}
@Override
public String toString() {
return "Student{" +
"num=" + num +
", name='" + name + '\'' +
", age=" + age +
", socre=" + socre +
'}';
}
/**
* 按照学号大小进行排序
* @param o
* @return
*/
@Override
public int compareTo(Student o) {
int result = 0;
//第一种实现 @@@@@@@@@@@@@@@@@@@@标记1
result = this.socre - o.socre;
//第二种实现 @@@@@@@@@@@@@@@@@@@@标记2
//result = o.score - this.score;
if (result == 0)
//此处如果成绩相等则调用字符串name的compareTo方法,把相等的项进行排序
return this.name.compareTo(o.name);
return result;
}
}
Comparable_test类:
package Test;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
/**
* @author Fox
* @date 2021/11/26 14:30
*/
public class Comparable_test {
public static void main(String[] args) {
ArrayList<Student> list = new ArrayList<>();
list.add(new Student(1,"fox",20,88));
list.add(new Student(2,"fox1",20,94));
list.add(new Student(3,"fox2",20,91));
list.add(new Student(4,"fox3",20,85));
list.add(new Student(4,"fox4",20,88));
Collections.sort(list);
for (Student student : list) {
System.out.println(student);
}
}
}
第一种实现标记1的结果:
第二种实现标记2的结果:
注意: 上面的两种结果图显示,其中有两个成绩都为88的对象,因为我在实现Comparable接口的时候实现了两种排序规则,对于出现相同的元素后,那么再根据另外的属性再次进行排序。其中Student{num=1, name='fox', age=20, socre=88}
Student{num=4, name='fox4', age=20, socre=88}
就是在比较排序的结果。
3.关键点
- 1.使用collections.sort实现排序实际上还是调用Array.sort的方法。
- 2.很多人弄不清楚comparaTo方法的返回值正负和零的区别和作用 在这里实际上我也没有弄清楚,看了很多博客都说的摩棱两可,最后通过询问专业课老师得知了一种简单的判断升序和降序的方法。如果用当前对象减去传入的对象不管结果值是什么都是升序,如
return len1 - len2;
和result = this.socre - o.socre;
如果用传入的对象减去当前调用者对象,那么则表示为降序。如result = o.score - this.score;
升序和降序的结果图如上方标记一和标记二的图。
(关于返回值的区别我还没弄清楚,可能和Array.sort内部的排序算法有关联) - 3.此方法来实现Comparable接口有利有弊,有益的是我们可以自定义排序规则,按照那几个属性进行排序(如上述代码,先把score成绩进行排序,如果出现相同的再使用name字符串进行比较排序),一定的弊端就是写好了代码后如果代码量很大,此时需要需改排序规则的画,需要修改大量的源码。如果实现了接口并且重写了compareTo方法后,一般不推荐在使用此方法进行修改。
二、Comparator
1.概述
Comparator则表示一个比较器,实现了该接口的的类的对象是一个针对目标类的对象定义的比较器,一般情况,这个比较器将作为一个参数进行传递,作为一个参数传递到Collections.sort和Arrays.sort方法来指定某个类对象的排序方式,一般被称为外部实现。
对于没有实现Comparable接口的或者实现了接口但是现有的排序不满足现有的排序需求的情况可以使用Comparator来实现。
Comparator体现了一种策略模式(strategy design pattern),就是不改变对象自身,而用一个策略对象(strategy object)来改变它的行为。
2.Comparator实例
1.针对在上面已经实现Comparable的情况(已经实现的排序不满足现有排序需求)
Student类不展出来了上面有。
Comparator_test类:
/**
* @author Fox
* @date 2021/11/26 14:30
*/
public class Comparable_test {
public static void main(String[] args) {
ArrayList<Student> list = new ArrayList<>();
list.add(new Student(1,"fox",20,88));
list.add(new Student(2,"fox1",20,94));
list.add(new Student(3,"fox2",20,91));
list.add(new Student(4,"fox3",20,85));
list.add(new Student(4,"fox4",20,88));
Collections.sort(list, new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
int result = o1.getSocre() - o2.getSocre();
if (result == 0)
return o1.getNum() - o2.getNum();
return result;
}
});
for (Student student : list) {
System.out.println(student);
}
}
}
结果如下图:
注意: 此时使用外部比较器实现了新的排序,如果成绩相等那么通过比较num学号属性进行比较排序如下。
Student{num=1, name='fox', age=20, socre=88}
Student{num=4, name='fox4', age=20, socre=88}
2.自定义对象没有实现Comparable接口的情况。
Student类:
/**
* @author Fox
* @date 2021/11/26 14:30
*/
public class Student {
private int num;//学号
private String name;//姓名
private int age;//年龄
private int socre;//成绩
public Student() {
}
public Student(int num, String name, int age, int socre) {
this.num = num;
this.name = name;
this.age = age;
this.socre = socre;
}
public int getNum() {
return num;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public int getSocre() {
return socre;
}
@Override
public String toString() {
return "Student{" +
"num=" + num +
", name='" + name + '\'' +
", age=" + age +
", socre=" + socre +
'}';
}
}
Comparator_test类:
/**
* @author Fox
* @date 2021/11/26 16:16
*/
public class Comparator_test {
public static void main(String[] args) {
ArrayList<Test.Student> list = new ArrayList<>();
list.add(new Test.Student(1,"fox",20,88));
list.add(new Test.Student(2,"fox1",20,94));
list.add(new Test.Student(3,"fox2",20,91));
list.add(new Test.Student(4,"fox3",20,85));
list.add(new Test.Student(4,"fox4",20,88));
//此处使用了lambada表达式
Collections.sort(list, (o1, o2) -> {
//后一个对象减前一个对象为倒叙
int result = o2.getSocre() - o1.getSocre();
if (result == 0)
//如果出现相同,则让Num使用升序
return o1.getNum() - o2.getNum();
return result;
});
for (Student student : list) {
System.out.println(student);
}
}
}
结果如下:
注意: 此处使用了score升序,num降序的排列方式。
3.关键点
- 和上面Comparable方式一样,前一个对象减后一个对象可以理解为升序,后一个对象减前一个对象可以理解为降序。
- 传递的Comparator参数采用了匿名内部类实现,重写了compare方法。
- Comparator体现了一种策略模式(strategy design pattern),就是不改变对象自身,而用一个策略对象(strategy object)来改变它的行为。
总结
本文只是作者在学习过程中对两中方式的粗略理解,让人头疼的还是返回值在集合排序的过程中的区别和作用(还没有搞清楚),革命尚未成功,同志仍需努力。如有错误,望指正。