1.为什么写?
comparator 是javase中的接口,位于java.util包下,该接口抽象度极高,有必要掌握该接口的使用
大多数文章告诉大家comparator是用来排序,但我想说排序是comparator能实现的功能之一,他不仅限于排序
2.接口功能
该接口代表一个比较器,比较器具有可比性!大多数文章都写如何用comparator排序,是因为javase数组工具类和集合工具类中提供的sort方法sort就是使用Comparator接口来处理排序的,大家见久了都认为Comparator接口是用来排序的,按照java抽象的尿性来看,该接口如果为排序而生,应该叫Sortable,Sortor之类的名字吧!下面是javase一些使用到Comparator接口的地方:
Arrays.sort(T[],Comparator<? super T> c);
Collections.sort(List<T> list,Comparator<? super T> c);
3.使用场景
什么场景需要做比较,那么什么场景就是Comparator接口的用武之地,我总结的两个场景:
1. 排序,需要比较两个对象谁排在前谁排在后(排序也可以让类实现Comparable接口,实现后该类的实例也具有排序能力)。
2. 分组,需要比较两个对象是否是属于同一组。
3. 待补充
4.举个栗子
1.排序【着重】
List<Dog> collect1 = list.stream().sorted(Comparator.comparing(Dog::getName)).collect(Collectors.toList());
System.out.println("给狗狗按名字字母顺序排序:"+collect1);
List<Dog> collect2 = list.stream().sorted(Comparator.comparing(Dog::getName).reversed()).collect(Collectors.toList());
System.out.println("给狗狗按名字字母顺序排序(倒叙):"+collect2);
在List或数组中的对象如果没有实现Comparable接口时,那么就需要调用者为需要排序的数组或List设置一个Compartor,Compartor的compare方法用来告诉代码应该怎么去比较两个实例,然后根据比较结果进行排序
package com.binc.testspring.common.lamada;
import jdk.nashorn.internal.objects.annotations.Constructor;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import org.apache.poi.ss.formula.functions.T;
import org.junit.Test;
import java.util.*;
import java.util.stream.Collectors;
/**
* @author binc
* Comparator 比较器学习 一:SortTest: 利用comparator 实现排序
* 二: GroupTest 利用comparator 实现分组 (这个卸载另一个类中了)
*
*
*/
public class GroupTest {
public GroupTest() {
}
/**
* @author binc
* @Description:按条件分组()
* @param datas
* @param c 是否为同一组的判断标准 (分组的核心逻辑其实就是比较逻辑,通过比较之后得结果来进行分组)
* @return
*/
public static <T> List<List<T>> divider(Collection<T> datas, Comparator<? super T> c) {
List<List<T>> result = new ArrayList<List<T>>();
for (T t : datas) {
boolean isSameGroup = false;
for (int j = 0; j < result.size(); j++) { //第一次循环的时候,result.size() = 0 不会进入循环所以不用担心下标越界
if (c.compare(t, result.get(j).get(0)) == 0) {
isSameGroup = true;
result.get(j).add(t);
break;
}
}
if (!isSameGroup) {
// 创建
List<T> innerList = new ArrayList<T>();
result.add(innerList); //第一个循环的时候,result的两层结构都在这里优先插入数据了
innerList.add(t);
}
}
return result;
}
public static void main(String[] args) {
List<Apple> list = new ArrayList<>();
list.add(new Apple("红", 205));
list.add(new Apple("红", 131));
list.add(new Apple("绿", 248));
list.add(new Apple("绿", 153));
list.add(new Apple("黄", 119));
list.add(new Apple("黄", 224));
List<List<Apple>> byColors = divider(list, new Comparator<Apple>() {
@Override
public int compare(Apple o1, Apple o2) {
// 按颜色分组
return Objects.equals(o1.color,(o2.color))?0:11; // 这里返回0(相等) 非零(不相等) 两类值就行了; 关键是divider中有个判断是否等于0的判断!!
// return o1.color.compareTo(o2.color);//
}
});
System.out.println("按颜色分组" + byColors);
//按颜色分组[[Apple(color=红, weight=205), Apple(color=红, weight=131)], [Apple(color=绿, weight=248), Apple(color=绿, weight=153)], [Apple(color=黄, weight=119), Apple(color=黄, weight=224)]]
// 按重量级分组[[Apple(color=红, weight=205), Apple(color=绿, weight=248), Apple(color=黄, weight=224)], [Apple(color=红, weight=131), Apple(color=绿, weight=153), Apple(color=黄, weight=119)]]
List<List<Apple>> byWeight = divider(list, new Comparator<Apple>() {
@Override
public int compare(Apple o1, Apple o2) {
// 按重量级
return (o1.weight / 100 == o2.weight / 100) ? 0 : 1; // 这里返回0(相等) 非零(不相等) 两类值就行了; 关键是divider中有个判断是否等于0的判断!!
}
});
System.out.println("按重量级分组" + byWeight);
System.out.println("===========================================");
Map<String, List<Apple>> collect = list.stream().collect(Collectors.groupingBy(Apple::getColor));
System.out.println("lamada表达式,按颜色分组:"+collect);
// lamada表达式,按颜色分组:{红=[Apple(color=红, weight=205), Apple(color=红, weight=131)], 黄=[Apple(color=黄, weight=119), Apple(color=黄, weight=224)], 绿=[Apple(color=绿, weight=248), Apple(color=绿, weight=153)]}
Map<Integer, List<Apple>> collect1 = list.stream().collect(Collectors.groupingBy(m -> m.getWeight() / 100));
System.out.println("lamada表达式,按重量/100分组:"+collect1);
//lamada表达式,按重量/100分组:{1=[Apple(color=红, weight=131), Apple(color=绿, weight=153), Apple(color=黄, weight=119)], 2=[Apple(color=红, weight=205), Apple(color=绿, weight=248), Apple(color=黄, weight=224)]}
}
@Test
public void test(){
List<List<T>> result = new ArrayList<List<T>>();
System.out.println(result.get(0));
System.out.println(result.get(0).get(0));
}
}
/*******************************下面的都是辅助代码不重要***********************************/
@Data
@ToString
@AllArgsConstructor
@NoArgsConstructor
class Apple{
public String color;
public int weight;
}
2.分组
Map<String, List<Apple>> collect = list.stream().collect(Collectors.groupingBy(Apple::getColor));
System.out.println("lamada表达式,按颜色分组:"+collect);
// lamada表达式,按颜色分组:{红=[Apple(color=红, weight=205), Apple(color=红, weight=131)], 黄=[Apple(color=黄, weight=119), Apple(color=黄, weight=224)], 绿=[Apple(color=绿, weight=248), Apple(color=绿, weight=153)]}
Map<Integer, List<Apple>> collect1 = list.stream().collect(Collectors.groupingBy(m -> m.getWeight() / 100));
System.out.println("lamada表达式,按重量/100分组:"+collect1);
//lamada表达式,按重量/100分组:{1=[Apple(color=红, weight=131), Apple(color=绿, weight=153), Apple(color=黄, weight=119)], 2=[Apple(color=红, weight=205), Apple(color=绿, weight=248), Apple(color=黄, weight=224)]}
使用Comparator和for循环处理列表,来进行分类;通过调用者实现Comparator接口的比较逻辑,来告诉程序应该怎么比较,通过比较之后得结果来进行分组。比如生活中的拳击比赛,会有公斤级的概念,那么程序中应该实现的处理逻辑是只要两个人的体重在同一个区间则为同一组公斤级的选手。下面例子中分别按照狗狗的颜色和体重级别两个维度来进行分组,因此分组的核心逻辑其实就是比较逻辑。相面我抽了一个工具方法:dividerList,第一个参数为需要处理的数据源,第二参数是分组时的比较逻辑。
代码挺有意思的,可以视情况而定而用
package com.binc.testspring.common.lamada;
import jdk.nashorn.internal.objects.annotations.Constructor;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import org.apache.poi.ss.formula.functions.T;
import org.junit.Test;
import java.util.*;
/**
* @author binc
* Comparator 比较器学习 一:SortTest: 利用comparator 实现排序
* 二: GroupTest 利用comparator 实现分组 (这个卸载另一个类中了)
*
*
*/
public class GroupTest {
public GroupTest() {
}
/**
* @author binc
* @Description:按条件分组()
* @param datas
* @param c 是否为同一组的判断标准 (分组的核心逻辑其实就是比较逻辑,通过比较之后得结果来进行分组)
* @return
*/
public static <T> List<List<T>> divider(Collection<T> datas, Comparator<? super T> c) {
List<List<T>> result = new ArrayList<List<T>>();
for (T t : datas) {
boolean isSameGroup = false;
for (int j = 0; j < result.size(); j++) { //第一次循环的时候,result.size() = 0 不会进入循环所以不用担心下标越界
if (c.compare(t, result.get(j).get(0)) == 0) {
isSameGroup = true;
result.get(j).add(t);
break;
}
}
if (!isSameGroup) {
// 创建
List<T> innerList = new ArrayList<T>();
result.add(innerList); //第一个循环的时候,result的两层结构都在这里优先插入数据了
innerList.add(t);
}
}
return result;
}
public static void main(String[] args) {
List<Apple> list = new ArrayList<>();
list.add(new Apple("红", 205));
list.add(new Apple("红", 131));
list.add(new Apple("绿", 248));
list.add(new Apple("绿", 153));
list.add(new Apple("黄", 119));
list.add(new Apple("黄", 224));
List<List<Apple>> byColors = divider(list, new Comparator<Apple>() {
@Override
public int compare(Apple o1, Apple o2) {
// 按颜色分组
return Objects.equals(o1.color,(o2.color))?0:11; // 这里返回0(相等) 非零(不相等) 两类值就行了; 关键是divider中有个判断是否等于0的判断!!
// return o1.color.compareTo(o2.color);//
}
});
System.out.println("按颜色分组" + byColors);
//按颜色分组[[Apple(color=红, weight=205), Apple(color=红, weight=131)], [Apple(color=绿, weight=248), Apple(color=绿, weight=153)], [Apple(color=黄, weight=119), Apple(color=黄, weight=224)]]
// 按重量级分组[[Apple(color=红, weight=205), Apple(color=绿, weight=248), Apple(color=黄, weight=224)], [Apple(color=红, weight=131), Apple(color=绿, weight=153), Apple(color=黄, weight=119)]]
List<List<Apple>> byWeight = divider(list, new Comparator<Apple>() {
@Override
public int compare(Apple o1, Apple o2) {
// 按重量级
return (o1.weight / 100 == o2.weight / 100) ? 0 : 1; // 这里返回0(相等) 非零(不相等) 两类值就行了; 关键是divider中有个判断是否等于0的判断!!
}
});
System.out.println("按重量级分组" + byWeight);
}
@Test
public void test(){
List<List<T>> result = new ArrayList<List<T>>();
System.out.println(result.get(0));
System.out.println(result.get(0).get(0));
}
}
/*******************************下面的都是辅助代码不重要***********************************/
@Data
@ToString
@AllArgsConstructor
@NoArgsConstructor
class Apple{
public String color;
public int weight;
}
5.总结
一般需要做比较的逻辑都可以使用的上Comparator,最常用的场景就是排序和分组,排序常使用Arrays和Collections的sort方法,而分组则可以使用上面提供的divider方法。
排序和分组的区别在于:
排序时,两个对象比较的结果有三种:大于,等于,小于。
分组时,两个对象比较的结果只有两种:等于(两个对象属于同一组),不等于(两个对象属于不同组)
6 推荐文章:https://blog.csdn.net/apsilk/article/details/90700985
7 Comparable 对比 Comparator 【重要】
1 Comparable是排序接口。若一个类实现了Comparable接口,就意味着该类支持排序。
实现了Comparable接口的类的对象的列表或数组可以通过Collections.sort或Arrays.sort进行自动排序。[实现Comparable,需要重写compareTo方法,compareTo方法中就是该类对象的比较(排序)规则,如果没有这个的话,就不知道这个类是按照什么东西进行排序得了] public class CardInfoVo implements Comparable
{ / compareTo 方法判断这个对象相对于给定对象o 的顺序,并且当这个对象小于、等于或 大于给定对象o 时,分别返回负整数、0或正整数*/ @Override
public int compareTo(CardInfoVo o) {
return this.defaultStatus-o.defaultStatus; **// 按照CardInfoVo的defaultStatus的升序排序的
//return o.id-this.id; // 这个是按照CardInfoVo的id属性降序排列的
}
2 Comparator是比较接口,我们如果需要控制某个类的次序,而该类本身不支持排序(即没有实现Comparable接口),那么我们就可以建立一个“该类的比较器”来进行排序,这个“比较器”只需要实现Comparator接口即可。
package com.binc.testspring.common.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class Bird implements Comparable<Bird>{
int age;
String name;
@Override
public int compareTo(Bird o) {
return this.age - o.age;
}
public static void main(String[] args) {
List<Bird> list = new ArrayList<>();
list.add(new Bird(2, "2s"));
list.add(new Bird(1, "1s"));
list.add(new Bird(0, "0s"));
list.add(new Bird(5, "5s"));
System.out.println(list);
System.out.println("------------------");
Collections.sort(list); // 由于Bird实现了Comparable 并重写了compareto方法;所以Bird的集合能够用collections.sort进行比较排序了;
System.out.println(list);
System.out.println("=================");
list.sort(Comparator.comparing(Bird::getAge).reversed()); // 加入Bird没有实现Comparable的话,Bird的集合无法进行排序的(不知道按照什么排),所以这里是利用了自定义的比较器进行比对排序;
System.out.println(list);
}
}
不同之处:
1 排序规则实现的方法不同
Comparable接口的方法:compareTo(Object o)
Comparator接口的方法:compare(T o1, To2)
2 类设计前后不同
Comparable接口用于在类的设计中使用;设计初期,就实现这个借口,指定排序方式。
Comparator接口用于类设计已经完成,还想排序(Arrays)。
int compare(T o1, T o2) 是“比较o1和o2的大小”。返回“负数”,意味着“o1比o2小”;返回“零”,意味着“o1等于o2”;返回“正数”,意味着“o1大于o2”
Comparable相当于“内部比较器”,而Comparator相当于“外部比较器”。
两种方法各有优劣, 用Comparable 简单, 只要实现Comparable 接口的对象直接就成为一个可以比较的对象,但是需要修改源代码。
用Comparator 的好处是不需要修改源代码, 而是另外实现一个比较器, 当某个自定义的对象需要作比较的时候,把比较器和对象一起传递过去就可以比大小了, 并且在Comparator 里面用户可以自己实现复杂的可以通用的逻辑,使其可以匹配一些比较简单的对象,可以节省很多重复劳动。
8 摘抄 【这个总结也挺好】
Comparable java.lang.Comparable 接口定义的 compareTo() 方法用于提供对其实现类的对象进行整体排序所需要的比较逻辑。
实现类基于 compareTo() 方法的排序被称为自然排序。而 compareTo() 方法的排序被称为它的自然排序。具体的排序原则可由实现类根据需要而定。用户在重写 compareTo() 方法以定制比较逻辑时,需要确保其余等价性判断方法 equals() 保持一致,即 e1.equals((Object)e2) 和e1.compareTo((Object)e2)==0 具有相同的值,这样的话我们就称自然顺序就和 equals 一致。
这个接口有什么用呢? 如果一个数组中的对象实现了 Compareable 接口,则对这个数组进行排序非常简单: Arrays.sort(); 如果 List 实现了该接口的话 , 我们就可以调用Collections.sort 或者 Arrays 方法给他们排序。实际上 Java 平台库中的所有值类 (value classes) 都实现了 Compareable 接口。
Comparable 接口只有一个方法 compareTo(Object obj)
其中 this < obj 返回负 this = obj 返回 0 this > obj 返回正
即将当前这个对象与指定的对象进行顺序比较,当该对象小于、等于或大于指定对象时,分别返回一个负整数、 0 或正整数,如果无法进行比较,则抛出ClassCastException 异常。
其实,有两种方式可以进行集合排序 :
- 集合中对象的所属类实现了 java.lang.Comparable 接口
- 为集合指定比较器 java.lang.Comparator 的实现类
Comparator , Comparable 接口的区别是:【重要!!!】
comparable 是通用的接口,用户可以实现它来完成自己特定的比较, 而 comparator 可以看成一种算法的实现,在需要容器集合 collection 需要比较功能的时候,来指定这个比较器,这可以看出一种设计模式,将算法和数据分离,就像 C++ STL 中的函数对象一样。
前者应该比较固定,和一个具体类相绑定,而后者比较灵活,它可以被用于各个需要比较功能的类使用。可以说前者属于“静态绑定”,而后者可以“动态绑定”。
一个类实现了 Camparable 接口表明这个类的对象之间是可以相互比较的。如果用数学语言描述的话就是这个类的对象组成的集合中存在一个全序。这样,这个类对象组成的集合就可以使用 Sort 方法排序了。
而 Comparator 的作用有两个:
1. 如果类的设计师没有考虑到 Compare 的问题而没有实现 Comparable 接口,可以通过 Comparator 来实现比较算法进行排序
- 为了使用不同的排序标准做准备,比如:升序、降序或其他什么序