前言

   对数据进行排序是平常经常会用到的操作之一,使用Jav8排序可以减少你在排序这方面的代码量,优化你的代码。

测试用例代码

定义个实体类User,拥有姓名name,年龄age,积分credits三个属性,定义一个包含User的集合,用于排序,下面是代码

/* 这里偷个懒,用lombok注解生成实体类getset等一些基本方法 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private String name;
    private Integer age;
    private Integer credits;
}

初始化待排序的集合

    private List<User> users = Lists.newArrayList(
            new User("jack",17,10),
            new User("jack",18,10),
            new User("jack",19,11),
            new User("apple",25,15),
            new User("tommy",23,8),
            new User("jessica",15,13)
            );
排序

对年龄从小到大排序

Before Java8

根据User年龄从小到大排序,使用Collections.sort方法,通过Comparator的匿名内部类实现

    @Test
    public void traditionCompareByName(){
        Collections.sort(users, new Comparator<User>() {
            @Override
            public int compare(User o1, User o2) {
                return o1.getAge() - o2.getAge();
            }
        });

        for (User user : users) {
            System.out.println(user);
        }
    }

结果

User(name=jessica, age=15, credits=13)
User(name=jack, age=17, credits=10)
User(name=jack, age=18, credits=10)
User(name=jack, age=19, credits=11)
User(name=tommy, age=23, credits=8)
User(name=apple, age=25, credits=15)

Process finished with exit code 0

in Java8

这里使用lambda表达式来代替匿名内部类,并且使用list接口下的sort方法(java8新增加),再链式输出

    @Test
    public void traditionCompareByNameInJava8(){
        users.sort((o1, o2) -> o1.getAge() - o2.getAge());
        users.forEach(user -> System.out.println(user));
    }

输出结果就不再显示了
当然还可以通过方法引用进一步的简化,这里使用Comparator下的comparingInt进行排序,使用User::getAge获得年龄,默认从小到大正向排序

import static java.util.Comparator.comparingInt;

    @Test
    public void traditionCompareByNameInJava8(){
        users.sort(comparingInt(User::getAge));
        users.forEach(System.out::println);
    }

对比

简单对比一下,可以发现使用Java8的排序对于简单的排序无论是从代码量还是可以阅都是比之前的匿名内部类实现compare方法要好的。

对年龄从大到小排序(反向排序)

Before Java8

同样是通过匿名内部类这是这次将 o1 - o2 的顺序调换一下

    @Test
    public void traditionCompareByNameReverse(){
        Collections.sort(users, new Comparator<User>() {
            @Override
            public int compare(User o1, User o2) {
                return o2.getAge() - o1.getAge();
            }
        });
    }

In Java8

和匿名内部类的颠倒一样,只是这里有之间的类库反转

    @Test
    public void traditionCompareByNameInJava8Reverse(){
        users.sort((o1, o2) -> o1.getAge() - o2.getAge());
    }

在比较器后面增加reversed即可,链式调用是java8的风格之一

    @Test
    public void traditionCompareByNameInJava8Reverse(){
        users.sort(comparingInt(User::getAge).reversed());
    }

同样是阅读性,原先的匿名内部类方法不仅阅读困难,一个简单的倒序也需要先去观察o2-o1还是o1-o2才能得出,而Java8的方法不仅代码简洁,可读性还很高,compare getAge读出是通过年龄进行排序,reversed读出是倒序。

根据姓名,年龄,积分排序

按照姓名,年龄与积分的顺序依次排序,也就是多条件组合排序

Before Java8

让我们看看传统的方式该如何实现

    @Test
    public void traditionCombinationCompare(){
        Collections.sort(users, new Comparator<User>() {
            @Override
            public int compare(User o1, User o2) {
                if (o1.getName().equals(o2.getName())) {
                    if (o1.getAge().equals(o2.getAge())) {
                        return o1.getAge() - o2.getAge();
                    } else {
                        return o1.getCredits() - o2.getCredits();
                    }
                } else {
                    return o1.getName().compareTo(o2.getName());
                }
            }
        });
    }

这样的代码我相信谁都不太想看,我自己写完都需要验证一遍以保证真的没有哪里逻辑写错,这样的做法不仅效率底下,还容易犯错,这种代码更是他人的噩梦。

in Java8

  • 在这里我们使用比较器的thenComparing实现链式调用
    @Test
    public void traditionCombinationCompareInJava8(){
        users.sort(comparing(User::getName)
                .thenComparing(User::getAge)
                .thenComparing(User::getCredits));
    }

可读性也很好,这样的代码几乎连注释都省去了,很清晰的可以看出排序的顺序,修改起来也很容易,而上面的代码如果要修改成另外一种次序,整个嵌套逻辑结构条件都要改动。

  • 另外如果需求变成如下,按照姓名顺序->年龄倒序->积分顺序的次序来排序,Java8也十分容易,comparing比较器提供了重载方法,可以自定义某条属性的排序,例子如下
    @Test
    public void traditionCombinationCompareInJava8(){
        users.sort(comparing(User::getName)
                .thenComparing(User::getAge, (o1, o2) -> o2 - o1)
                .thenComparing(User::getCredits));
    }
  • update(10-24)
    事实上 o2 - o1 这样的代码还是有一些命令式的风格,即包含了具体的实现过程(o2 -o1这样的代码),thenComparaing方法可以直接接受一个排序器,因此我们只要直接将倒序的排序器当做参数传入即可,代码如下
    @Test
    public void traditionCombinationCompareInJava8(){
        users.sort(comparing(User::getName)
                .thenComparing(comparing(User::getAge).reversed())
                .thenComparing(User::getCredits));
        users.forEach(System.out::println);
    }

很清晰的可以看到第二行的getAge是倒序,而其他的属性依旧是正序,建议大家使用链式写法的时候像上面一样分行,提高可读性

总结
  • 使用lambda表达式可以代替传统的匿名内部类,精简代码量,提高可读性,可以进一步使用方法引用继续精简
  • 使用Comparing的比较器加上链式调用可以很方便的完成逆序,多属性组合排序等排序情况,代码精简,阅读性高
  • 使用链式调用建议按照.功能分行写,便于阅读