我们来说说如何使用 Lambda 表达式实现排序功能

简介: 我是小假 期待与你的下一次相遇 ~

 目录

基于Comparator排序

使用 Lambda 表达式替换Comparator匿名内部类

通过静态方法抽取公共的 Lambda 表达式

借助Comparator的comparing方法

多条件排序

在Stream中进行排序

倒序排列

调转排序判断

在Comparator.comparing中定义排序反转

在Stream中定义排序反转

null 值的判断

元素是 null 的笨拙实现

使用Comparator.nullsLast和Comparator.nullsFirst

排序条件的字段是 null


基于Comparator排序

在 Java8 之前,都是通过实现Comparator接口完成排序,比如:

new Comparator<Student>() {
    @Override
    public int compare(Student h1, Student h2) {
        return h1.getName().compareTo(h2.getName());
    }
};

image.gif

这里展示的是匿名内部类的定义,如果是通用的对比逻辑,可以直接定义一个实现类。使用起来也比较简单,如下就是应用:

@Test
void baseSortedOrigin() {
    final List<Student> students = Lists.newArrayList(
            new Student("Tom", 10),
            new Student("Jerry", 12)
    );
    Collections.sort(students, new Comparator<Student>() {
        @Override
        public int compare(Student h1, Student h2) {
            return h1.getName().compareTo(h2.getName());
        }
    });
    Assertions.assertEquals(students.get(0), new Student("Jerry", 12));
}

image.gif

这里使用了 Junit5 实现单元测试,用来验证逻辑非常适合。

因为定义的Comparator是使用name字段排序,在 Java 中,String类型的排序是通过单字符的 ASCII 码顺序判断的,J排在T的前面,所以Jerry排在第一个。

使用 Lambda 表达式替换Comparator匿名内部类

使用过 Java8 的 Lamdba 的应该知道,匿名内部类可以简化为 Lambda 表达式为:

Collections.sort(students, (Student h1, Student h2) -> h1.getName().compareTo(h2.getName()));

image.gif

在 Java8 中,List类中增加了sort方法,所以Collections.sort可以直接替换为:

students.sort((Student h1, Student h2) -> h1.getName().compareTo(h2.getName()));

image.gif

根据 Java8 中 Lambda 的类型推断,可以将指定的Student类型简写:

students.sort((h1, h2) -> h1.getName().compareTo(h2.getName()));

image.gif

至此,整段排序逻辑可以简化为:

@Test
void baseSortedLambdaWithInferring() {
    final List<Student> students = Lists.newArrayList(
            new Student("Tom", 10),
            new Student("Jerry", 12)
    );
    students.sort((h1, h2) -> h1.getName().compareTo(h2.getName()));
    Assertions.assertEquals(students.get(0), new Student("Jerry", 12));
}

image.gif

通过静态方法抽取公共的 Lambda 表达式

可以在Student中定义一个静态方法:

public static int compareByNameThenAge(Student s1, Student s2) {
    if (s1.name.equals(s2.name)) {
        return Integer.compare(s1.age, s2.age);
    } else {
        return s1.name.compareTo(s2.name);
    }
}

image.gif

这个方法需要返回一个int类型参数,在 Java8 中,可以在 Lambda 中使用该方法:

@Test
void sortedUsingStaticMethod() {
    final List<Student> students = Lists.newArrayList(
            new Student("Tom", 10),
            new Student("Jerry", 12)
    );
    students.sort(Student::compareByNameThenAge);
    Assertions.assertEquals(students.get(0), new Student("Jerry", 12));
}

image.gif

借助Comparator的comparing方法

在 Java8 中,Comparator类新增了comparing方法,可以将传递的Function参数作为比较元素,比如:

@Test
void sortedUsingComparator() {
    final List<Student> students = Lists.newArrayList(
            new Student("Tom", 10),
            new Student("Jerry", 12)
    );
    students.sort(Comparator.comparing(Student::getName));
    Assertions.assertEquals(students.get(0), new Student("Jerry", 12));
}

image.gif

多条件排序

在静态方法一节中展示了多条件排序,还可以在Comparator匿名内部类中实现多条件逻辑:

@Test
void sortedMultiCondition() {
    final List<Student> students = Lists.newArrayList(
            new Student("Tom", 10),
            new Student("Jerry", 12),
            new Student("Jerry", 13)
    );
    students.sort((s1, s2) -> {
        if (s1.getName().equals(s2.getName())) {
            return Integer.compare(s1.getAge(), s2.getAge());
        } else {
            return s1.getName().compareTo(s2.getName());
        }
    });
    Assertions.assertEquals(students.get(0), new Student("Jerry", 12));
}

image.gif

从逻辑来看,多条件排序就是先判断第一级条件,如果相等,再判断第二级条件,依次类推。在 Java8 中可以使用comparing和一系列thenComparing表示多级条件判断,上面的逻辑可以简化为:

@Test
void sortedMultiConditionUsingComparator() {
    final List<Student> students = Lists.newArrayList(
            new Student("Tom", 10),
            new Student("Jerry", 12),
            new Student("Jerry", 13)
    );
    students.sort(Comparator.comparing(Student::getName).thenComparing(Student::getAge));
    Assertions.assertEquals(students.get(0), new Student("Jerry", 12));
}

image.gif

这里的thenComparing方法是可以有多个的,用于表示多级条件判断,这也是函数式编程的方便之处。

在Stream中进行排序

Java8 中,不但引入了 Lambda 表达式,还引入了一个全新的流式 API:Stream API,其中也有sorted方法用于流式计算时排序元素,可以传入Comparator实现排序逻辑:

@Test
void streamSorted() {
    final List<Student> students = Lists.newArrayList(
            new Student("Tom", 10),
            new Student("Jerry", 12)
    );
    final Comparator<Student> comparator = (h1, h2) -> h1.getName().compareTo(h2.getName());
    final List<Student> sortedStudents = students.stream()
            .sorted(comparator)
            .collect(Collectors.toList());
    Assertions.assertEquals(sortedStudents.get(0), new Student("Jerry", 12));
}

image.gif

同样的,可以通过 Lambda 简化书写:

@Test
void streamSortedUsingComparator() {
    final List<Student> students = Lists.newArrayList(
            new Student("Tom", 10),
            new Student("Jerry", 12)
    );
    final Comparator<Student> comparator = Comparator.comparing(Student::getName);
    final List<Student> sortedStudents = students.stream()
            .sorted(comparator)
            .collect(Collectors.toList());
    Assertions.assertEquals(sortedStudents.get(0), new Student("Jerry", 12));
}

image.gif

倒序排列

调转排序判断

排序就是根据compareTo方法返回的值判断顺序,如果想要倒序排列,只要将返回值取返即可:

@Test
void sortedReverseUsingComparator2() {
    final List<Student> students = Lists.newArrayList(
            new Student("Tom", 10),
            new Student("Jerry", 12)
    );
    final Comparator<Student> comparator = (h1, h2) -> h2.getName().compareTo(h1.getName());
    students.sort(comparator);
    Assertions.assertEquals(students.get(0), new Student("Tom", 10));
}

image.gif

可以看到,正序排列的时候,是h1.getName().compareTo(h2.getName()),这里直接倒转过来,使用的是h2.getName().compareTo(h1.getName()),也就达到了取反的效果。在 Java 的Collections中定义了一个java.util.Collections.ReverseComparator内部私有类,就是通过这种方式实现元素反转。

借助**Comparator**的**reversed**方法倒序

在 Java8 中新增了reversed方法实现倒序排列,用起来也是很简单:

@Test
void sortedReverseUsingComparator() {
    final List<Student> students = Lists.newArrayList(
            new Student("Tom", 10),
            new Student("Jerry", 12)
    );
    final Comparator<Student> comparator = (h1, h2) -> h1.getName().compareTo(h2.getName());
    students.sort(comparator.reversed());
    Assertions.assertEquals(students.get(0), new Student("Tom", 10));
}

image.gif

在Comparator.comparing中定义排序反转

import com.google.common.collect.Lists;
import org.junit.jupiter.api.Test;
import java.util.Comparator;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
class Student {
    private String name;
    private int age;
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public int getAge() {
        return age;
    }
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        return age == student.age && name.equals(student.name);
    }
    @Override
    public int hashCode() {
        return name.hashCode() + age;
    }
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
public class ComparatorTest {
    @Test
    void sortedUsingComparatorReverse() {
        final List<Student> students = Lists.newArrayList(
                new Student("Tom", 10),
                new Student("Jerry", 12)
        );
        // 使用Comparator.comparing的重载方法,传入Comparator.reverseOrder()实现倒序
        students.sort(Comparator.comparing(Student::getName, Comparator.reverseOrder()));
        // 验证排序结果
        assertEquals(students.get(0), new Student("Jerry", 12));
    }
}

image.gif

在Stream中定义排序反转

在Stream中的操作与直接列表排序类似,可以反转Comparator定义,也可以使用Comparator.reverseOrder()反转。实现如下:

@Test
void streamReverseSorted() {
    final List<Student> students = Lists.newArrayList(
            new Student("Tom", 10),
            new Student("Jerry", 12)
    );
    final Comparator<Student> comparator = (h1, h2) -> h2.getName().compareTo(h1.getName());
    final List<Student> sortedStudents = students.stream()
            .sorted(comparator)
            .collect(Collectors.toList());
    Assertions.assertEquals(sortedStudents.get(0), new Student("Tom", 10));
}
@Test
void streamReverseSortedUsingComparator() {
    final List<Student> students = Lists.newArrayList(
            new Student("Tom", 10),
            new Student("Jerry", 12)
    );
    final List<Student> sortedStudents = students.stream()
            .sorted(Comparator.comparing(Student::getName, Comparator.reverseOrder()))
            .collect(Collectors.toList());
    Assertions.assertEquals(sortedStudents.get(0), new Student("Tom", 10));
}

image.gif

null 值的判断

前面的例子中都是有值元素排序,能够覆盖大部分场景,但有时候还是会碰到元素中存在null的情况:

  1. 列表中的元素是 null
  2. 列表中的元素参与排序条件的字段是 null

如果还是使用前面的那些实现会碰到NullPointException异常,即 NPE,简单演示一下:

@Test
void sortedNullGotNPE() {
    final List<Student> students = Lists.newArrayList(
            null,
            new Student("Snoopy", 12),
            null
    );
    Assertions.assertThrows(NullPointerException.class,
            () -> students.sort(Comparator.comparing(Student::getName)));
}

image.gif

所以,需要考虑这些场景。

元素是 null 的笨拙实现

最先想到的就是判空:

@Test
void sortedNullNoNPE() {
    final List<Student> students = Lists.newArrayList(
            null,
            new Student("Snoopy", 12),
            null
    );
    students.sort((s1, s2) -> {
        if (s1 == null) {
            return s2 == null ? 0 : 1;
        } else if (s2 == null) {
            return -1;
        }
        return s1.getName().compareTo(s2.getName());
    });
    Assertions.assertNotNull(students.get(0));
    Assertions.assertNull(students.get(1));
    Assertions.assertNull(students.get(2));
}

image.gif

可以将判空的逻辑抽取出一个Comparator,通过组合方式实现:

class NullComparator<T> implements Comparator<T> {
    private final Comparator<T> real;
    NullComparator(Comparator<? super T> real) {
        this.real = (Comparator<T>) real;
    }
    @Override
    public int compare(T a, T b) {
        if (a == null) {
            return (b == null) ? 0 : 1;
        } else if (b == null) {
            return -1;
        } else {
            return (real == null) ? 0 : real.compare(a, b);
        }
    }
}

image.gif

在 Java8 中已经准备了这个实现。

使用Comparator.nullsLast和Comparator.nullsFirst

使用Comparator.nullsLast实现null在结尾:

@Test
void sortedNullLast() {
    final List<Student> students = Lists.newArrayList(
            null,
            new Student("Snoopy", 12),
            null
    );
    students.sort(Comparator.nullsLast(Comparator.comparing(Student::getName)));
    Assertions.assertNotNull(students.get(0));
    Assertions.assertNull(students.get(1));
    Assertions.assertNull(students.get(2));
}

image.gif

使用Comparator.nullsFirst实现null在开头:

@Test
void sortedNullFirst() {
    final List<Student> students = Lists.newArrayList(
            null,
            new Student("Snoopy", 12),
            null
    );
    students.sort(Comparator.nullsFirst(Comparator.comparing(Student::getName)));
    Assertions.assertNull(students.get(0));
    Assertions.assertNull(students.get(1));
    Assertions.assertNotNull(students.get(2));
}

image.gif

是不是很简单,接下来看下如何实现排序条件的字段是 null 的逻辑。

排序条件的字段是 null

这个就是借助Comparator的组合了,就像是套娃实现了,需要使用两次Comparator.nullsLast,这里列出实现:

@Test
void sortedNullFieldLast() {
    final List<Student> students = Lists.newArrayList(
            new Student(null, 10),
            new Student("Snoopy", 12),
            null
    );
    final Comparator<Student> nullsLast = Comparator.nullsLast(
            Comparator.nullsLast( // 1
                    Comparator.comparing(
                            Student::getName,
                            Comparator.nullsLast( // 2
                                    Comparator.naturalOrder() // 3
                            )
                    )
            )
    );
    students.sort(nullsLast);
    Assertions.assertEquals(students.get(0), new Student("Snoopy", 12));
    Assertions.assertEquals(students.get(1), new Student(null, 10));
    Assertions.assertNull(students.get(2));
}

image.gif

代码逻辑如下:

  1. 代码 1 是第一层 null-safe 逻辑,用于判断元素是否为 null;
  2. 代码 2 是第二层 null-safe 逻辑,用于判断元素的条件字段是否为 null;
  3. 代码 3 是条件Comparator,这里使用了Comparator.naturalOrder(),是因为使用了String排序,也可以写为String::compareTo。如果是复杂判断,可以定义一个更加复杂的Comparator,组合模式就是这么好用,一层不够再套一层。




如果小假的内容对你有帮助,请点赞,评论,收藏。创作不易,大家的支持就是我坚持下去的动力!

image.gif 编辑

相关文章
|
Java
【Java基础】Java8 使用 stream().sorted()对List集合进行排序
【Java基础】Java8 使用 stream().sorted()对List集合进行排序
1246 0
|
Prometheus 监控 安全
SpringBoot Actuator未授权访问漏洞的解决方法
SpringBoot Actuator未授权访问漏洞的解决方法Actuator 是 SpringBoot 提供的用来对应用系统进行自省和监控的功能模块,借助于 Actuator 开发者可以很方便地对应用系统某些监控指标进行查看、统计等。
33562 0
|
Java Apache Maven
【异常解决】Handler dispatch failed;nested exception is java.lang.NoClassDefFoundError: org/apache/common
【异常解决】Handler dispatch failed;nested exception is java.lang.NoClassDefFoundError: org/apache/common
11574 0
|
SQL 缓存 安全
深入解析MyBatis-Plus LambdaQueryWrapper与QueryWrapper:高效数据查询的秘密
深入解析MyBatis-Plus LambdaQueryWrapper与QueryWrapper:高效数据查询的秘密
15663 2
|
人工智能 开发框架 Java
重磅发布!AI 驱动的 Java 开发框架:Spring AI Alibaba
随着生成式 AI 的快速发展,基于 AI 开发框架构建 AI 应用的诉求迅速增长,涌现出了包括 LangChain、LlamaIndex 等开发框架,但大部分框架只提供了 Python 语言的实现。但这些开发框架对于国内习惯了 Spring 开发范式的 Java 开发者而言,并非十分友好和丝滑。因此,我们基于 Spring AI 发布并快速演进 Spring AI Alibaba,通过提供一种方便的 API 抽象,帮助 Java 开发者简化 AI 应用的开发。同时,提供了完整的开源配套,包括可观测、网关、消息队列、配置中心等。
12066 140
|
存储 关系型数据库 MySQL
在 MySQL 中使用 Insert Into Select
【8月更文挑战第11天】
8292 0
在 MySQL 中使用 Insert Into Select
|
算法 Java 测试技术
mybatis-plus雪花算法生成Id使用详解
mybatis-plus雪花算法生成Id使用详解
6436 0
mybatis-plus雪花算法生成Id使用详解
|
存储 Java 数据库连接
mybatis精讲(三)--标签及TypeHandler使用
mybatis精讲(三)--标签及TypeHandler使用
|
存储 缓存 Java
SpringCache缓存
SpringCache缓存
509 0
|
存储 缓存 NoSQL
SpringBoot配置第三方专业缓存技术jetcache远程缓存方案和本地缓存方案
SpringBoot配置第三方专业缓存技术jetcache远程缓存方案和本地缓存方案
1938 0