【小家Java】聊聊Java中的比较器(排序):Comparable和Comparator;Spring中的Comparators和AnnotationAwareOrderComparator(上)

简介: 【小家Java】聊聊Java中的比较器(排序):Comparable和Comparator;Spring中的Comparators和AnnotationAwareOrderComparator(上)

前言


“顺序“在任何一个领域里都是非常重要的一个概念,程序也不例外。不同的执行顺序,能对你的执行结果产生直接影响。


既然涉及到顺序,那就要求排序。所以本文讨论的就是排序中使用到的比较器Comparable和Comparator。


Comparable和Comparator都是java.包下的两个接口,从字面上看这两个接口都是用来做比较用的,但是jdk里面不可能定义两个功能相同的接口,所以他们肯定有不同的用处。


JDK中的Comparable和 Comparator


Comparable和Comparator接口都是为了对类进行比较,众所周知,诸如Integer,double等基本数据类型,java可以对他们进行比较,而对于类的比较,需要人工定义比较用到的字段比较逻辑。


Comparable

Comparable可以认为是一个内比较器,实现了Comparable接口的类有一个特点,就是这些 类是可以和自己比较的。

若一个类实现了Comparable接口,就意味着该类支持排序。实现了Comparable接口的类的对象的列表或数组可以通过Collections.sort或Arrays.sort进行自动排序。

此外,**实现此接口的对象可以用作有序映射中的键或有序集合中的集合,无需指定比较器。**该接口定义如下:

// @since 1.2 出现得还是稍微偏晚的
public interface Comparable<T> {
  // 这里入参也是T  所以是自己和自己比较的
  // 规则:this和t比较。  返回0表示两个对象相等
  // 返回正数: this > o
  // 返回负数: this < o
    public int compareTo(T o);
}


比如如下例子:


    public static void main(String[] args) {
        Integer[] intArr = {new Integer(2), new Integer(1), new Integer(9), new Integer(5)};
        System.out.println("排序前:" + StringUtils.arrayToCommaDelimitedString(intArr));  //排序前:2,1,9,5
        Arrays.sort(intArr);
        System.out.println("排序后:" + StringUtils.arrayToCommaDelimitedString(intArr)); //排序后:1,2,5,9
    }


Comparator


Comparator是比较接口,我们如果需要控制某个类的次序,而该类本身不支持排序(即没有实现Comparable接口),那么我们就可以建立一个“该类的比较器”来进行排序,这个“比较器”只需要实现Comparator接口即可。

个人认为有两种情况可以使用实现Comparator接口的方式:


  1. 对象不支持自己和自己比较(没有实现Comparable接口),但是又想对两个对象进行比较(大都是这种情况)
  2. 对象实现了Comparable接口,但是开发者认为compareTo方法中的比较方式并不是自己想要的那种比较方式

// @since 1.2  JDK8该接口增加了很多默认方法,后面也会讲解
@FunctionalInterface
public interface Comparator<T> {
  int compare(T o1, T o2);
    boolean equals(Object obj);
}


若一个类要实现Comparator接口:它一定要实现compare(T o1, T o2) 函数,但可以不实现 equals(Object obj) 函数。

int compare(T o1, T o2) 是“比较o1和o2的大小”。返回“负数”,意味着“o1比o2小”;返回“零”,意味着“o1等于o2”;返回“正数”,意味着“o1大于o2”。


现在我们自定义一个类Person,然后给Person类自定义一个比较器。

public class Person {
    public String name;
    public Integer age;
}
// person类的Compartor比较器  泛型类型为Person
// 按照
public class PersonCompartor implements Comparator<Person> {
    @Override
    public int compare(Person o1, Person o2) {
        return o1.getAge()-o2.getAge();
    }
}
// 测试
    public static void main(String[] args) {
        Person[] people = new Person[]{new Person("fsx", 18), new Person("dy", 15)};
        System.out.println("排序前:" + StringUtils.arrayToCommaDelimitedString(people));
        Arrays.sort(people, new PersonCompartor()); // 使用自定义的比较器排序
        System.out.println("排序后:" + StringUtils.arrayToCommaDelimitedString(people));
    }
结果:
排序前:Person{name='fsx', age=18},Person{name='dy', age=15}
排序后:Person{name='dy', age=15},Person{name='fsx', age=18}


可以看到完全按照我们自己定义的比较器排序了。并且,并且,并且Person是没有实现排序接口的哦,所以**是没有侵入性的**。


Comparable和Comparator区别比较


  1. Comparable是排序接口,若一个类实现了Comparable接口,就意味着“该类支持排序”。而Comparator是比较器,我们若需要控制某个类的次序,可以建立一个“该类的比较器”来进行排序。解耦了~~
  2. Comparable相当于“内部比较器”,而Comparator相当于“外部比较器”。
  3. 个性化比较:如果实现类没有实现Comparable接口,又想对两个类进行比较(或者实现类实现了Comparable接口,但是对compareTo方法内的比较算法不满意),那么可以实现Comparator接口,自定义一个比较器,写比较算法。
  4. Comparable接口是 java.lang包下的 而 Comparator接口才是java.util包下的。(由此课件后者被归类为一种工具)


两种方法各有优劣, 用Comparable 简单, 只要实现Comparable 接口的对象直接就成为一个可以比较的对象,但是需要修改源代码。(有侵入性)

用Comparator 的好处是不需要修改源代码, 而是另外实现一个比较器, 当某个自定义的对象需要作比较的时候,把比较器和对象一起传递过去就可以比大小了, 并且在Comparator里面用户可以自己实现复杂的可以通用的逻辑,使其可以匹配一些比较简单的对象,那样就可以节省很多重复劳动了。


Comparator接口中的默认方法和静态方法


JDK1.8后,在此接口中Comparator定义了好些个静态方法和默认方法,很多时候我们能够当作工具来使用。


default方法属于实例的,static方法属于类的(当然实例也可使用)

  // 逆序排序 用于集合的排序~
    default Comparator<T> reversed() {
        return Collections.reverseOrder(this);
    }
// Demo
    public static void main(String[] args) {
        Person[] people = new Person[]{new Person("fsx", 18), new Person("dy", 15)};
        System.out.println("排序前:" + StringUtils.arrayToCommaDelimitedString(people));
        Arrays.sort(people, new PersonCompartor().reversed()); // 使用自定义的比较器排序
        System.out.println("排序后:" + StringUtils.arrayToCommaDelimitedString(people));
    }
// Demo结果:这样reversed一下 就逆序了~~~
排序前:Person{name='fsx', age=18},Person{name='dy', age=15}
排序后:Person{name='fsx', age=18},Person{name='dy', age=15}
  // 可以很方便的实现两层排序:比如先按照年龄排序  再按照名字排序等等~~~~
    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);
        };
    }
    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));
    }
    default Comparator<T> thenComparingInt(ToIntFunction<? super T> keyExtractor) {
        return thenComparing(comparingInt(keyExtractor));
    }
    default Comparator<T> thenComparingLong(ToLongFunction<? super T> keyExtractor) {
        return thenComparing(comparingLong(keyExtractor));
    }
    default Comparator<T> thenComparingDouble(ToDoubleFunction<? super T> keyExtractor) {
        return thenComparing(comparingDouble(keyExtractor));
    }
// Demo:
    public static void main(String[] args) {
        Person[] people = new Person[]{new Person("fsx", 18), new Person("dy", 15)};
        System.out.println("排序前:" + StringUtils.arrayToCommaDelimitedString(people));
        Arrays.sort(people, new PersonCompartor().thenComparing(Person::getName)); // 使用自定义的比较器排序
        System.out.println("排序后:" + StringUtils.arrayToCommaDelimitedString(people));
    }


下面看看静态方法:


    public static <T extends Comparable<? super T>> Comparator<T> reverseOrder() {
        return Collections.reverseOrder();
    }
    // 按照自然排序的一个比较器
    public static <T extends Comparable<? super T>> Comparator<T> naturalOrder() {
        return (Comparator<T>) Comparators.NaturalOrderComparator.INSTANCE;
    }
    // 它俩是对比较器进行了包装,对null友好了
    public static <T> Comparator<T> nullsFirst(Comparator<? super T> comparator) {
        return new Comparators.NullComparator<>(true, comparator);
    }
    public static <T> Comparator<T> nullsLast(Comparator<? super T> comparator) {
        return new Comparators.NullComparator<>(false, comparator);
    }
// Demo
    public static void main(String[] args) {
      // 放置一个null值
        Person[] people = new Person[]{new Person("fsx", 18), null, new Person("dy", 15)};
        System.out.println("排序前:" + StringUtils.arrayToCommaDelimitedString(people));
        Arrays.sort(people, Comparator.nullsFirst(new PersonCompartor())); // 使用自定义的比较器排序
        System.out.println("排序后:" + StringUtils.arrayToCommaDelimitedString(people));
    }
// 结果  把null放在了第一位
排序前:Person{name='fsx', age=18},null,Person{name='dy', age=15}
排序后:null,Person{name='dy', age=15},Person{name='fsx', age=18}
    // 它只需要一个函数,所以要求你取出来的这个字段是实现了Comparable接口的,所以你从泛型约束中也能看出来
    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 方法一不同的是 该方法多了一个参数 keyComparator ,keyComparator 是创建一个自定义的比较器  注意是只比较的是key
    // 比如这样子:Arrays.sort(people, Comparator.comparing(Person::getAge, (a1, a2) -> a2 - a1)); // 使用自定义的比较器排序
    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));
    }
  // Arrays.sort(people, Comparator.comparingInt(Person::getAge));  他们不能自定义比较器了
    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));
    }



相关文章
|
4天前
|
Java Nacos 开发者
Java从入门到精通:4.2.1学习新技术与框架——以Spring Boot和Spring Cloud Alibaba为例
Java从入门到精通:4.2.1学习新技术与框架——以Spring Boot和Spring Cloud Alibaba为例
|
4天前
|
Dubbo Java 应用服务中间件
Java从入门到精通:3.2.2分布式与并发编程——了解分布式系统的基本概念,学习使用Dubbo、Spring Cloud等分布式框架
Java从入门到精通:3.2.2分布式与并发编程——了解分布式系统的基本概念,学习使用Dubbo、Spring Cloud等分布式框架
|
10天前
|
Java 关系型数据库 MySQL
一套java+ spring boot与vue+ mysql技术开发的UWB高精度工厂人员定位全套系统源码有应用案例
UWB (ULTRA WIDE BAND, UWB) 技术是一种无线载波通讯技术,它不采用正弦载波,而是利用纳秒级的非正弦波窄脉冲传输数据,因此其所占的频谱范围很宽。一套UWB精确定位系统,最高定位精度可达10cm,具有高精度,高动态,高容量,低功耗的应用。
一套java+ spring boot与vue+ mysql技术开发的UWB高精度工厂人员定位全套系统源码有应用案例
|
11天前
|
负载均衡 Java 开发者
细解微服务架构实践:如何使用Spring Cloud进行Java微服务治理
【4月更文挑战第17天】Spring Cloud是Java微服务治理的首选框架,整合了Eureka(服务发现)、Ribbon(客户端负载均衡)、Hystrix(熔断器)、Zuul(API网关)和Config Server(配置中心)。通过Eureka实现服务注册与发现,Ribbon提供负载均衡,Hystrix实现熔断保护,Zuul作为API网关,Config Server集中管理配置。理解并运用Spring Cloud进行微服务治理是现代Java开发者的关键技能。
|
12天前
|
安全 Java 数据安全/隐私保护
使用Spring Security进行Java身份验证与授权
【4月更文挑战第16天】Spring Security是Java应用的安全框架,提供认证和授权解决方案。通过添加相关依赖到`pom.xml`,然后配置`SecurityConfig`,如设置用户认证信息和URL访问规则,可以实现应用的安全保护。认证流程包括请求拦截、身份验证、响应生成和访问控制。授权则涉及访问决策管理器,如基于角色的投票。Spring Security为开发者构建安全应用提供了全面且灵活的工具,涵盖OAuth2、CSRF保护等功能。
|
13天前
|
Java 大数据 云计算
Spring框架:Java后台开发的核心
【4月更文挑战第15天】Spring框架在Java后台开发中占据核心位置,因其控制反转(IoC)、面向切面编程(AOP)、事务管理等特性提升效率和质量。Spring提供数据访问集成、RESTful Web服务和WebSocket支持。优势包括高效开发、灵活扩展、强大生态圈和广泛应用。应用于企业级应用、微服务架构及云计算大数据场景。掌握Spring对Java开发者至关重要。
|
16天前
|
Java 应用服务中间件 Maven
使用IDEA搭建SpringMVC环境,Maven导入了依赖,但是运行报错 java.lang.ClassNotFoundException
使用IDEA搭建SpringMVC环境,Maven导入了依赖,但是运行报错 java.lang.ClassNotFoundException
14 1
|
24天前
|
存储 安全 Java
Spring Security应用讲解(Java案列演示)
Spring Security应用讲解(Java案列演示)
|
25天前
|
前端开发 安全 Java
使用Java Web框架:Spring MVC的全面指南
【4月更文挑战第3天】Spring MVC是Spring框架的一部分,用于构建高效、模块化的Web应用。它基于MVC模式,支持多种视图技术。核心概念包括DispatcherServlet(前端控制器)、HandlerMapping(请求映射)、Controller(处理请求)、ViewResolver(视图解析)和ModelAndView(模型和视图容器)。开发流程涉及配置DispatcherServlet、定义Controller、创建View、处理数据、绑定模型和异常处理。
使用Java Web框架:Spring MVC的全面指南
|
26天前
|
XML Java 程序员
作为Java程序员还不知道Spring中Bean创建过程和作用?
作为Java程序员还不知道Spring中Bean创建过程和作用?
15 0