为什么写?
comparator是javase中的接口,位于java.util包下,该接口抽象度极高,有必要掌握该接口的使用- 大多数文章告诉大家
comparator是用来排序,但我想说排序是comparator能实现的功能之一,他不仅限于排序
接口功能
Comparator接口代表一个比较器,比较器具有可比性!平时我们大多数都是使用改接口(Comparator)实现对集合,排序。这是
因为JAVASE数组工具类和集合工具类中提供的sort方法sort就是使用Comparator接口来处理排序的,但是Comparator接口并不只是
用来排序的,下面是JAVASE一些使用到Comparator接口的地方:
Arrays.sort(T[],Comparator<? super T> c);Collections.sort(List<T> list,Comparator<? super T> c);
使用场景
什么场景需要做比较,那么什么场景就是Comparator接口的用武之地,我总结的两个场景:
- 排序,需要比较两个对象谁排在前谁排在后(排序也可以让类实现Comparable接口,实现后该类的实例也具有排序能力)。
- 分组,需要比较两个对象是否是属于同一组。
排序场景
在List或数组中的对象如果没有实现Comparable接口时,那么就需要调用者为需要排序的数组或List设置一个Compartor,Compartor的compare方法用来告诉代码应该怎么去比较两个实例,然后根据比较结果进行排序。
条件排序:公共代码
public class SortTest {@Data@AllArgsConstructor@ToStringclass Dog{ //内部类public int age;public String name;public String num;}List<Dog> list= new ArrayList<Dog>(){{add(new Dog(5, "DogA","001"));add(new Dog(5, "DogB","002"));add(new Dog(5, "DogC","003"));add(new Dog(9, "DogA","004"));add(new Dog(35, "DogF","005"));add(new Dog(74, "Dogg","006"));}};}
单一条件排序
Comparator实现排序(按照年龄,名字)
/***单一条件排序*/@Testpublic void test1(){//按照年龄排序Collections.sort(list, new Comparator<Dog>() {//实现compare(T o1, To2) 方法,返回正数,零,负数各代表大于,等于小于@Overridepublic int compare(Dog o1, Dog o2) {//return o2.age - o1.age; //排序规则----升序return String.valueOf(o1.getAge()).compareTo(String.valueOf(o2.getAge())); //compareTo()传String}});System.out.println("给狗狗按照年龄倒序:"+list);}@Testpublic void test2(){//按照名字排序Collator comparator = Collator.getInstance(Locale.CANADA);Collections.sort(list, new Comparator<Dog>() {@Overridepublic int compare(Dog o1, Dog o2) {// return o1.name.compareTo(o2.name);return comparator.compare(o1.getName(),o2.getName());}});System.out.println("给狗狗按名字字母顺序排序:"+list);}
Lambda优化实现排序
/***使用Lambda表达式优化比较器代码(单一条件排序)*/@Testpublic void test3() {//对学生集合按年龄进行排序Collections.sort(list,(s1, s2) -> (s1.getAge() - s2.getAge()) );}
多条件排序
Comparator实现排序(按照年龄和名字)
/***多条件排序*/@Testpublic void test4() {Collections.sort(list,new Comparator<Dog>() {@Overridepublic int compare(Dog s1, Dog s2) {int flag;// 首选按年龄升序排序flag = s1.getAge()-s2.getAge();if(flag==0){// 如果年龄按编号降序排序flag = s2.getNum().compareTo(s1.getNum());}return flag;}});list.forEach(System.out::println);}
Lambda优化实现多条件排序
/***多条件排序 ----使用lambda表达式优化*/@Testpublic void test5() {Collections.sort(list,(s1,s2)->{int flag;// 首选按年龄升序排序flag = s1.getAge()-s2.getAge();// 方案一、判断是否为空 --- 为空执行lambda表达式,返回一个对象flag = Optional.ofNullable(flag == 0 ? null: flag).orElseGet(() -> s2.getNum().compareTo(s1.getNum()));//方案二、if(flag==0){// 如果年龄按编号降序排序flag =s2.getNum().compareTo(s1.getNum());}//最终返回return flag;});list.forEach(System.out::println);}
自定义条件排序
自定义条件排序公共代码
/***自定义条件排序*///定义排序规则 通过asList()方法将数组转为list集合String[] order = {"语文","数学","英语","物理","化学","生物","政治","历史","地理","总分"};final List<String> definedOrder = Arrays.asList(order);//需要排序的数据List<String> listClass = new ArrayList<String>(){{add("总分");add("英语");add("政治");add("总分");add("数学");}};
自定义条件排序方案一 Comparator
//自定义条件排序方案一@Testpublic void test6(){Collections.sort(listClass,new Comparator<String>() {@Overridepublic int compare(String o1, String o2) {//int indexOf(String str) :返回第一次出现的指定子字符串在此字符串中的索引。int io1 = definedOrder .indexOf(o1);int io2 = definedOrder .indexOf(o2);return io1-io2;}});for(String s:listClass){System.out.print(s+" ");}//打印结果:数学 英语 政治 总分 总分}
自定义条件排序方案二 Lambda
//自定义条件排序方案二 使用Lambda表达式优化比较器代码@Testpublic void test7(){Collections.sort(listClass,(s1,s2)->definedOrder.indexOf(s1) - definedOrder.indexOf(s2));listClass.forEach(System.out::print);}
分组场景
使用Comparator和for循环处理列表,来进行分类;通过调用者实现Comparator接口的比较逻辑,来告诉程序应该怎么比较,通过比较之后得结果来进行分组。
———————————————————————————————————————————————————————————————————————————————————————-
下面例子中分别按照狗狗的颜色和体重级别两个维度来进行分组,因此分组的核心逻辑其实就是比较逻辑。相面我抽了一个工具方法:dividerList,第一个参数为需要处理的数据源,第二参数是分组时的比较逻辑。
公共代码
@Data@AllArgsConstructor@ToStringclass Apple {public String color;public int weight;}List<Apple> list = new ArrayList<Apple>(){{add(new Apple("红", 205));add(new Apple("红", 131));add(new Apple("绿", 248));add(new Apple("绿", 22));add(new Apple("黄", 119));add(new Apple("黄", 224));add(new Apple("白", 2024));}};/*** 是否为同一组的判断标准 参数一:需要处理的数据源 datas,参数二分组时的比较逻辑c*/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++) {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);innerList.add(t);}}return result;}
Comparator实现按颜色分组
@Testpublic void test1(){List<List<Apple>> byColors = divider(list, new Comparator<Apple>() {//按照颜色基本进行分组@Overridepublic int compare(Apple o1, Apple o2) {// 按颜色分组return o1.color.compareTo(o2.color);}});System.out.println("按颜色分组" + byColors);}
Lambda优化按颜色分组
/***使用lambda优化(按照颜色分组)*/@Testpublic void test1_1() {//按照颜色重量级分组divider_Lambda(list, (o1, o2) -> o1.color.compareTo(o2.color)).forEach(System.out::println);}
Comparator实现按分组
@Testpublic void test2(){//按照重量级进行分组List<List<Apple>> byWeight = divider(list, new Comparator<Apple>() {@Overridepublic int compare(Apple o1, Apple o2) {// 按重量级return (o1.getWeight() / 100 == o2.getWeight() / 100) ? 0 : 1;}});byWeight.forEach(x-> System.out.println("按照总量分组:"+x));//System.out.println("按重量级分组" + byWeight);}
Lambda优化按重量级分组
/***使用lambda优化(按照重量级进行分组)*/@Testpublic void test2_1() {//按照颜色分组divider_Lambda(list, (o1, o2) -> (o1.getWeight() / 100 == o2.getWeight() / 100) ? 0 : 1).forEach(System.out::println);}
Comparator接口常用的方法
公共代码
实体类
Persons实体类
package com.zy.pagehelper.model;import lombok.Builder;import lombok.Data;import lombok.NoArgsConstructor;import java.math.BigDecimal;@Data@NoArgsConstructor@Builderpublic class Persons implements Comparable {private String name;private BigDecimal age;private Integer height;private Student student;public Persons(String name, BigDecimal age, Integer height) {this.name = name;this.age = age;this.height = height;this.student = new Student(0);}public Persons(String name, BigDecimal age, Integer height, Student student) {this.name = name;this.age = age;this.height = height;this.student = student;}@Overridepublic int compareTo(Object o) {Persons p1 = (Persons) o;if (this.age.equals(p1.age)) {return p1.height - this.height;}return this.age.compareTo(p1.age);}}
Student实体类
package com.zy.pagehelper.model;import lombok.AllArgsConstructor;import lombok.Data;import lombok.NoArgsConstructor;import java.util.Comparator;@Data@AllArgsConstructor@NoArgsConstructorpublic class Student implements Comparator {private int age;@Overridepublic int compare(Object o1, Object o2) {Student p1 = (Student) o1;Student p2 = (Student) o2;int result = Integer.compare(p1.age, p2.age);result = result == 0 ? ((p1.age > p2.age) ? 1 : -1) : result;return result;}}
公共集合
Persons persons = new Persons();List<Persons> personList = new ArrayList<Persons>() {{add(new Persons("a", new BigDecimal(12), 170));add(new Persons("b", new BigDecimal(24), 175, new Student(27)));add(new Persons("c", new BigDecimal(12), 177));add(new Persons("a", new BigDecimal(12), 177));add(new Persons("b", new BigDecimal(54), 174, new Student(19)));}};
naturalOrder 方法
naturalOrder - 自然比较,根据实体类定义的Comparable进行比较!
示例代码:
@Testpublic void testNaturalOrder(){// naturalOrder 自然比较,根据实体类定义的ComparableSystem.out.println("naturalOrder : ");personList.sort(Comparator.naturalOrder());personList.forEach(System.out::println);}
comparing方法
comparing、comparingLong、comparingInt、comparingDouble - 常用比较方法,可以指定参数类型
comparing方法参数是一个函数式接口keyExtractor,意识即为指定排序对象中的排序键,这里注意排序键这里标注了Comparable接口
comparing方法
/*** Accepts a function that extracts a {@link java.lang.Comparable* Comparable} sort key from a type {@code T}, and returns a {@code* Comparator<T>} that compares by that sort key.** <p>The returned comparator is serializable if the specified function* is also serializable.** @apiNote* For example, to obtain a {@code Comparator} that compares {@code* Person} objects by their last name,**<pre>{@code* Comparator<Person> byLastName = Comparator.comparing(Person::getLastName);* }</pre>*/public static <T, U extends Comparable<? super U>> Comparator<T> comparing(Function<? super T, ? extends U> keyExtractor){Objects.requireNonNull(keyExtractor);return (Comparator<T> & Serializable)(c1, c2) -> keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2));}
comparing方法参数是一个函数式接口keyExtractor,意识即为指定排序对象中的排序键,这里注意排序键这里标注了Comparable接口。
同时我们也可以看到有重载的comparing方法:
/*** Accepts a function that extracts a sort key from a type {@code T}, and* returns a {@code Comparator<T>} that compares by that sort key using* the specified {@link Comparator}.** <p>The returned comparator is serializable if the specified function* and comparator are both serializable.** @apiNote* For example, to obtain a {@code Comparator} that compares {@code* Person} objects by their last name ignoring case differences,**<pre>{@code* Comparator<Person> cmp = Comparator.comparing(* Person::getLastName,* String.CASE_INSENSITIVE_ORDER);* }</pre>*/public static <T, U> Comparator<T> comparing(Function<? super T, ? extends U> keyExtractor,Comparator<? super U> keyComparator){Objects.requireNonNull(keyExtractor);Objects.requireNonNull(keyComparator);return (Comparator<T> & Serializable)(c1, c2) -> keyComparator.compare(keyExtractor.apply(c1),keyExtractor.apply(c2));}
第二个参数也很好理解,提取完sort key之后,要定义关于这个key的Comparator,在注释中的例子也比较好理解。 这里有个小tips:在String类中,提供了一个实现Comparator接口的常量来标识不对语言敏感的字典序排序器。
/*** A Comparator that orders {@code String} objects as by* {@code compareToIgnoreCase}. This comparator is serializable.* <p>* Note that this Comparator does <em>not</em> take locale into account,* and will result in an unsatisfactory ordering for certain locales.* The java.text package provides <em>Collators</em> to allow* locale-sensitive ordering.** @see java.text.Collator#compare(String, String)* @since 1.2*/public static final Comparator<String> CASE_INSENSITIVE_ORDER= new CaseInsensitiveComparator();private static class CaseInsensitiveComparatorimplements Comparator<String>, java.io.Serializable {// use serialVersionUID from JDK 1.2.2 for interoperabilityprivate static final long serialVersionUID = 8575799808933029326L;public int compare(String s1, String s2) {int n1 = s1.length();int n2 = s2.length();int min = Math.min(n1, n2);for (int i = 0; i < min; i++) {char c1 = s1.charAt(i);char c2 = s2.charAt(i);if (c1 != c2) {c1 = Character.toUpperCase(c1);c2 = Character.toUpperCase(c2);if (c1 != c2) {c1 = Character.toLowerCase(c1);c2 = Character.toLowerCase(c2);if (c1 != c2) {// No overflow because of numeric promotionreturn c1 - c2;}}}}return n1 - n2;}/** Replaces the de-serialized object. */private Object readResolve() { return CASE_INSENSITIVE_ORDER; }}// 这里其实可以看到compareToIgnoreCase也是调用了这个实例的compare方法public int compareToIgnoreCase(String str) {return CASE_INSENSITIVE_ORDER.compare(this, str);}
在Comparator接口中,也直接提供了具体类型的三个comparing方法:
public static <T> Comparator<T> comparingInt(ToIntFunction<? super T> keyExtractor) {Objects.requireNonNull(keyExtractor);return (Comparator<T> & Serializable)(c1, c2) -> Integer.compare(keyExtractor.applyAsInt(c1), keyExtractor.applyAsInt(c2));}public static <T> Comparator<T> comparingLong(ToLongFunction<? super T> keyExtractor) {Objects.requireNonNull(keyExtractor);return (Comparator<T> & Serializable)(c1, c2) -> Long.compare(keyExtractor.applyAsLong(c1), keyExtractor.applyAsLong(c2));}public static<T> Comparator<T> comparingDouble(ToDoubleFunction<? super T> keyExtractor) {Objects.requireNonNull(keyExtractor);return (Comparator<T> & Serializable)(c1, c2) -> Double.compare(keyExtractor.applyAsDouble(c1), keyExtractor.applyAsDouble(c2));}
测试示例代码:
@Testpublic void testComparing(){//comparing、comparingLong、comparingInt、comparingDouble - 常用比较方法,可以指定参数类型// comparing 1.0 比较集合中对象的年龄,取最大值Optional<Persons> optional = personList.stream().max(Comparator.comparing(Persons::getAge));System.out.println("comparing 1.0 : get max age " + optional.get().toString() + "\n");// comparing 2.1optional = personList.stream().max(Comparator.comparing(Persons::getName, Comparator.reverseOrder()));System.out.println("comparing 2.1 : get min name " + optional.get().toString() + "\n");// comparing 2.2optional = personList.stream().max(Comparator.comparing(Persons::getName, String::compareTo));System.out.println("comparing 2.2 : get max name " + optional.get().toString() + "\n");// comparing 2.3 该方法多了一个参数 keyComparator ,keyComparator 是创建一个自定义的比较器。示例种:通过cmmpare()方法进行学生年龄比较,optional = personList.stream().max(Comparator.comparing(Persons::getStudent, (o1, o2) -> new Student().compare(o1, o2)));System.out.println("comparing 2.3 : get max student.age " + optional.get().toString() + "\n");}/*打印结果comparing 1.0 : get max age Persons(name=b, age=54, height=174, student=Student(age=19))comparing 2.1 : get min name Persons(name=a, age=12, height=170, student=Student(age=0))comparing 2.2 : get max name Persons(name=c, age=12, height=177, student=Student(age=0))comparing 2.3 : get max student.age Persons(name=b, age=24, height=175, student=Student(age=27))*/// 升序 comparing方法的具体实现一comparingIntSystem.out.println("升序 : ");personList.sort(Comparator.comparingInt(Persons::getHeight));personList.forEach(System.out::println);// 降序 comparing方法的具体实现二comparingIntSystem.out.println("降序 : ");personList.sort(Comparator.comparingInt(Persons::getHeight).reversed());personList.forEach(System.out::println);
thenComparing方法
/*** Returns a lexicographic-order comparator with another comparator.* If this {@code Comparator} considers two elements equal, i.e.* {@code compare(a, b) == 0}, {@code other} is used to determine the order.** <p>The returned comparator is serializable if the specified comparator* is also serializable.** @apiNote* For example, to sort a collection of {@code String} based on the length* and then case-insensitive natural ordering, the comparator can be* composed using following code,**<pre>{@code* Comparator<String> cmp = Comparator.comparingInt(String::length)* .thenComparing(String.CASE_INSENSITIVE_ORDER);* }</pre>*/default Comparator<T> thenComparing(Comparator<? super T> other) {Objects.requireNonNull(other);return (Comparator<T> & Serializable) (c1, c2) -> {int res = compare(c1, c2);return (res != 0) ? res : other.compare(c1, c2);};}
从方法名称上知道这是当比较相同时的使用的一个排序规则,这里需要注意看具体实现是会先调用比较器实例中的compare方法来进行比较一轮,当结果等于0的时候才会调用other这个比较器规则进行比较。比如下面的一个DOME:
List<String> strings = Arrays.asList("def", "abc", "hel", "world");strings.sort(Comparator.comparingInt(String::length).reversed() //(1).thenComparing(String::compareToIgnoreCase) // (2).thenComparing(Comparator.reverseOrder()) // (3)这个比较器不会被应用 因为比较器(2)已经把结果比较出来了,并且没有相等的结果,这里不会再应用(3)比较器);System.out.println(strings); // 输出[world, abc, def, hel]
当然因为有了 comparing方法的支持,所以也就有了下面两个thenComparing的重载方法
default <U extends Comparable<? super U>> Comparator<T> thenComparing(Function<? super T, ? extends U> keyExtractor){return thenComparing(comparing(keyExtractor));}
default <U> Comparator<T> thenComparing(Function<? super T, ? extends U> keyExtractor,Comparator<? super U> keyComparator){return thenComparing(comparing(keyExtractor, keyComparator));}
测试示例代码:
@Testpublic void test2(){//根据第一个属性进行排序,如果相同则以此根据下一个thenComparing()中的属性进行排序// thenComparing 1.0System.out.println("thenComparing 1.0 : ");personList.sort(Comparator.comparing(Persons::getAge));personList.forEach(System.out::println);// thenComparing 1.1System.out.println("thenComparing 1.1 : ");personList.sort(Comparator.comparing(Persons::getAge).thenComparing(Persons::getHeight));personList.forEach(System.out::println);// thenComparing 2.0System.out.println("thenComparing 2.0 : ");personList.sort(Comparator.comparing(Persons::getAge).thenComparing(Persons::getHeight).thenComparing(Persons::getName));personList.forEach(System.out::println);}
nullsLast()/nullsFirst()
Comparator接口中有两个对null友好的比较器方法:
/*** Returns a null-friendly comparator that considers {@code null} to be* less than non-null. When both are {@code null}, they are considered* equal. If both are non-null, the specified {@code Comparator} is used* to determine the order. If the specified comparator is {@code null},* then the returned comparator considers all non-null values to be equal.** <p>The returned comparator is serializable if the specified comparator* is serializable.** @param <T> the type of the elements to be compared* @param comparator a {@code Comparator} for comparing non-null values* @return a comparator that considers {@code null} to be less than* non-null, and compares non-null objects with the supplied* {@code Comparator}.* @since 1.8*/public static <T> Comparator<T> nullsFirst(Comparator<? super T> comparator) {return new Comparators.NullComparator<>(true, comparator);}// null比非null元素都大的public static <T> Comparator<T> nullsLast(Comparator<? super T> comparator) {return new Comparators.NullComparator<>(false, comparator);}
这里是通过Comparators这个工厂类提供的NullComparator比较器实现的,看到注释有一条需要注意是如果不指定comparator参数,即传入null,那么所有的非null参数都会被视为相等。
当集合中存在null元素时,可以使用针对null友好的比较器,null元素排在集合的最前面/最后面
测试示例代码
@Testpublic void testNulls() {// nullsLastSystem.out.println("nullsLast : ");personList.sort(Comparator.nullsLast(Comparator.comparing(Persons::getName)));personList.forEach(System.out::println);// nullsFirstSystem.out.println("nullsFirst : ");personList.sort(Comparator.nullsFirst(Comparator.comparing(Persons::getName)));personList.forEach(System.out::println);}
Comparator接口和Comparable接口
这两个接口首先要做一个简单区别。
Comparable接口
* Lists (and arrays) of objects that implement this interface can be sorted* automatically by {@link Collections#sort(List) Collections.sort} (and* {@link Arrays#sort(Object[]) Arrays.sort}). Objects that implement this* interface can be used as keys in a {@linkplain SortedMap sorted map} or as* elements in a {@linkplain SortedSet sorted set}, without the need to* specify a {@linkplain Comparator comparator}.<p>
可以看到注释中说明了实现了该接口的对象,在数组中可以使用Collections.sort或者Arrays.sort方法实现排序,或者实现了该接口的对象可以作为sortedMap或者SortedSet的key。这里也提到我们不用制定一个排序或者作为key的Comparator接口。
public interface Comparable<T> {/*** 省略部分注释* <p>The implementor must ensure <tt>sgn(x.compareTo(y)) ==* -sgn(y.compareTo(x))</tt> for all <tt>x</tt> and <tt>y</tt>. (This* implies that <tt>x.compareTo(y)</tt> must throw an exception iff* <tt>y.compareTo(x)</tt> throws an exception.)*/public int compareTo(T o);}
在compareTo方法上的注释中提到,必须确保x.compareTo(y)和y.compareTo(x)的结果是一致的,并且这也意味着当x.compartTo(y)抛出一个异常,那么y.compareTo(x)也应该去抛出一个异常,那么这里就思考到了一个关于null的设计:null.compareTo(obj)我们肯定知道会有NPE,那么你在实现compareTo方法的时候,如果obj.compareTo(null)这里也应该去抛出NPE。
这里就不去写具体的demo去演示了,这里理解为一个对象实现了Comparable接口,那么这个对象就是可比较的,并且在排序等场景下调用实现接口中的compareTo方法。
Comparator接口
Comparator接口要理解为比较器,实现其接口的类其实是比较器的一种实现,相当于一个比较的函数定义。来看下他的注释:
* A comparison function, which imposes a <i>total ordering</i> on some* collection of objects. Comparators can be passed to a sort method (such* as {@link Collections#sort(List,Comparator) Collections.sort} or {@link* Arrays#sort(Object[],Comparator) Arrays.sort}) to allow precise control* over the sort order. Comparators can also be used to control the order of* certain data structures (such as {@link SortedSet sorted sets} or {@link* SortedMap sorted maps}), or to provide an ordering for collections of* objects that don't have a {@link Comparable natural ordering}.<p>
这里我们看到Arrays、Collections也提供了重载的sort方法,支持传入一个集合/数组和Comparator接口的实例。当然当前列表/数组中的对象不一定是实现了Comparable接口。
类实现了comparable接口之后,可以直接调用排序方法;而当使用comparator时,不需要类实现,具体使用时(也就是调用某些方法时)的需要类和该comparator绑定起来来实现。comparable实现内部排序,Comparator是外部排序。
