关于java集合比较器的创建和使用
概述: 在java集合中,TreeSet集合和TreeMap集合底层数据结构都是自平衡二叉树,所以在这两个集合中添加元素的时候会实现自动排序,排序方式为中序排序(即左根右的方式进行排序,详情请见二叉树数据结构,这里不做赘述)。
SUN公司编写源代码的时候已经为我们写好了排序代码,因此在我们输入数据元素后会实现自动排序(这里的数据都是对象,也就是引用类型,不能是基本数据类型),源码中可见底层是调用了compareTo()方法进行了排序(详情请见TreeSet或者TreeMap源码)
如果我们要实现自定义的引用类型排序需要重写排序方法,就必须重写排序方法,有两个选择,一个是实现Comparable接口,另一个是实现Comparator接口,下面对两个接口进行介绍。
第一个比较器:实现 Comparable接口
Comparable接口:这个接口要在自定义的类上实现,比如定义了一个Person类,实现Comparator接口,除了该类应有的属性、构造器、方法外,我们需要实现该接口中的抽象方法 compareTo()并且需要指定其泛型(也就是要比较的引用数据的类型),意思就是说要比较的是泛型也就是<>中间的类型
其中方法内的编写规则是自己根据需求编写的,请看代码。
//比较器一:实现Comparable接口,在compareTo()方法中编写比较规则
class Person implements Comparable<Person>{
int age;
String name;
public Person(int age,String name){
this.age=age;
this.name=name;
}
public int compareTo(Person c){
if(c.age==this.age){ //如果年龄相同就按名字排序
return this.name.compareTo(c.name);
}else{ //年龄不同,按年龄排序
return this.age-c.age;
}
}
}
Person c的这个变量c就是增加数据的时候传入的Person对象,this.name和this.age都为之前添加的Person对象。
我们来看compareTo方法代码块return this.name.compareTo(c.name); 这里是当年龄相同的时候,用姓名来进行比较排序,而Person对象的name是个字符串,SUN公司已经重写了字符串的比较规则,这里直接调用即可
注意:这里调用的compareTo方法是String类中重写的compareTo方法,并不是我们重写的方法。
年龄不同的情况下可直接返回此时传来的Person对象的年龄和上一个年龄的差值
所谓比较规则,就是比较出两个数据的顺序,或者说是大小,所以这里运用相减的形式,若差值为正则说明上一个Person对象的年龄大,反之亦然。String重写的compareTo方法是按照字符所对应的ASCLL值进行排序的,感兴趣的小伙伴可以去源码看一下,这里就不做过多说明。
Comparable接口用法总结: 简单来说,要通过实现Comparable接口重写比较方法需要:
1.让要比较的类实现Comparable接口,并指定要比较的引用数据类型,文中是Person。
2.重写compareTo()方法即可,重写自己需要的比较规则。(程序执行时底层会自动调用,详情请看源码)
第二个比较器:Comparator接口
Comparator接口:这个接口需要我们重写定义一个类来实现它,我们可以称这个类为:比较器,比如我们定义了一个类(也就是比较器)来实现Comparator接口,并且需要指定比较类型类型(用泛型来制指定,在实现接口的时候指定)最简单的比较器,我们需要实现该接口中的抽象方法 compare()即可,只要实现了该比较方法就已经实现了比较器的功能,当然也可以加别的属性和方法,但是没必要,接下来请看代码。
//比较器二:实现Comparator接口,在compare()方法中编写比较规则
class MyComparator1 implements Comparator<Person>{
public int compare(Person p1,Person p2){
if(p1.age==p2.age){ //年龄相同用名字排序
return p2.name.compareTo(p1.name);
}else{ //年龄不同用年龄排序
return p1.age-p2.age;
}
}
}
这里的比较规则我们还是按照年龄相同则用名字来排序,年龄不同则用年龄来排序,p1和p2是存入TreeSet集合中的两个Person对象。
这里要注意一点:因为我们这个比较器是用其它类来实现的,而原本类中没有重写compare方法,所以我们需要在创建集合对象的时候将此类传入,如下代码所示
//第二种比较器,实现Comparator接口,重写compare()方法
// 要传入写好的比较器
Set<Person> set=new TreeSet<>(new MyComparator1()); //传入比较器
通过new 比较器名(也就是我们用来实现Comparator接口的类名)的方式来传入比较器。然后我们加入的数据就可以按照我们想要的比较规则就行排序了。
如果我们没有传入比较器的话,则会按照SUN所写的比较规则进行比较排序,这不是我们想要的,所以,一定要记住传入比较器。
Comparator接口用法总结:简单来说,要通过实现Comparator接口创建一个自定义的比较器需要
1.创建一个类并且实现Comparator接口,同时指定要比较的引用数据类型
2.重写compare()方法,重写自己需要的比较规则。
3.在创建集合对象的时候将比较器传入(如何传入请看上面的代码)
第三个比较器:一个特殊的比较器(匿名内部类方式)
为什么说它特殊呢,因为这个比较器是采用匿名内部类的方式实现,来看代码。
//第三种比较器,使用匿名内部类的方法,在创建TreeSet集合的时候new Comparator接口,指定要比较的引用类型,并实现compare()方法
Set<Person> set=new TreeSet<>(new Comparator<Person>(){
public int compare(Person p1,Person p2){
if(p1.age==p2.age){
return p1.name.compareTo(p2.name);
}else{
return p1.age-p2.age;
}
}
});
我们来对这个方法进行剖析,左边自然不用说,直接看其右边,在new TreeSet集合的时候new 了Comparator接口,我们知道Comparator接口中有抽象方法compare(),所以在new出了接口的时候实现其接口中没有实现的方法,在实现的compare()方法中编写比较规则。(这里说的new接口实际上不是真的new了一个接口,而是有一个类实现了这个接口,而我们实际上是new了这个类,但是这个类没有名字,因此它是匿名内部类,关于更多详情请查看匿名内部类的内容)
注意:在new接口的时候不要忘记指定泛型(也就是指定要比较的对象)
总结三个比较器的优缺点:
1. 实现Comparable接口的比较器,因为要在要比较的类上实现,所以比较规则只能重写一次,比较规则比较单一,适用于比较规则不变的时候使用此方法。
2. 实现Comparator接口的比较器,因为是新建一个类实现Comparator接口,而我们可以新建很多类来实现Comparator接口,所以可以写很多种不同的比较规则,适用于比较规则多变的情况,根据需要传入相应的比较器即可,比较灵活多变。
3. 匿名内部类的比较器,因为这个类没有名字,所以只能使用一次(这是匿名内部类的缺点),所以同样的这种方法也只适用于比较规则不变或者说很少发生改变的情况。
有用就好,喜欢就点赞,持续关注,精彩不断!
码字不易,不要白嫖,觉得有用的,可以给我点个赞,感谢!
因技术能力有限,如文中有不合理的地方,希望各位大佬指出,在下方评论留言,谢谢,希望大家一起进步,一起成长。
如需转载请注明来源,谢谢!