pkslow.com 南瓜慢说

  • AllArticles
  • Container
  • Spring
  • Life
  • Cloud
  • Collections
  • About
  • GitHub

  • Search
Terraform101 English Terraform Middleware config Go Private Kubernetes pkslow Test HTTPS Redis Docker Mac Plan Stream MongoDB Spring DevOps JVM String Map Set List Performance Email Springboot JavaCollections ArrayList Java

一图说尽排序,一文细说Sorting(Array、List、Stream的排序)

Created on: 2019-10-13 | Category: Java | 0 | View: 518

1 简说排序

排序是极其常见的使用场景,因为在生活中就有很多这样的实例。国家GDP排名、奥运奖牌排名、明星粉丝排名等,各大排行榜,给人的既是动力,也是压力。

而讲到排序,就会有各种排序算法和相关实现,本文不讲任何排序算法,而只专注于讲使用。通过实例给大家展示,我们可以了解怎样使用既有的工具进行排序。Linux之父说:

Talk is cheap. show me the code!

本文JDK版本为Java 8,但并不代表所介绍到的所有方法只能在JDK1.8上跑,部分方法在之前的版本就已经给出。

如下本次整理的图,记住图中的方法,就能轻松应对大多数场景,赶紧收藏起来吧,哈哈

Java-Sorting

2 两个接口

2.1 Comparable

先上代码:

package java.lang;

public interface Comparable<T> {
    public int compareTo(T o);
}

可以看出这个接口只有一个方法,这个方法只有一个参数,实现了这个接口的类就可以和同类进行比较了。这个方法所实现的,就是比较法则,也是说,它表示如何对两个对象进行比较。它返回的是一个整数int:

  • 返回正数,代表当前对象大于被比较的对象;
  • 返回0,代表当前对象等于于被比较的对象;
  • 返回负数,代表当前对象小于被比较的对象。

实现了该接口后,我们就可以使用Arrays.sort()和Collections.sort()来进行排序了。不然对象没有比较法则,程序肯定是不知道如何进行比较排序的。像我们常用的类String、Integer、Double、Date等,JDK都帮我们实现了Comparable接口,我们可以直接对这类对象进行比较排序。举个例子,Date Comparable的实现:

public int compareTo(Date anotherDate) {
    long thisTime = getMillisOf(this);
    long anotherTime = getMillisOf(anotherDate);
    return (thisTime<anotherTime ? -1 : (thisTime==anotherTime ? 0 : 1));
}

需要注意的是,当我们自己去实现Comparable接口时,一定要注意与**equals()**方法保持一致。当两个对象是equals的,compare的结果应该是相等的。

2.2 Comparator

还是先看代码,看看接口定义吧:

package java.util;

@FunctionalInterface
public interface Comparator<T> {
    int compare(T o1, T o2);
}

它是一个函数式接口,它的compare方法有两个参数,代表进行比较的两个对象。这个接口代表了可以作为某种对象比较的一种法则,或叫一种策略。它的返回值正负代表意义与Comparable接口的方法一样。它的使用通常会有三种方式:

  1. 实现类
  2. 匿名类
  3. Lambda表达式

在Java8之后,我们一般用Lambda比较多,也比较灵活优雅。

2.3 两个接口的比较

两个接口功能都是用于比较排序,但其实有很大的区别。

  1. 两者方法参数不同,Comparable只有一个参数,表示被比较的对象,因为它的方法是位于需要比较的类里的,所以只要一个参数就可以了;而Comparator的比较方法则有两个参数,分别表示比较对象和被比较对象。
  2. Comparable与对象绑定,位于对象内,我们可以称之为内比较器;而Comparator是独立于需要比较的类的,我们可以称为外比较器。
  3. 当类实现了Comparable方法后,比较法则就确定了,我们称之为自然比较方法,我们无法给它实现多种比较方法;而因为Comparator独立于外,我们可以为同一个类提供多种Comparator的实现,这样来提供多种比较方法/策略,如升序倒序,因此我们也可以将Comparator看成是一种策略模式。

相对于Comparable,Comparator有一定的灵活性,假如一个类并没有实现Comparable接口,并且这个类是无法修改的,我们就要通过提供Comparator来进行比较排序。Comparator这种模式实现了数据与算法的解耦合,对于维护也是很方便的。

3 工具类

十分友好的是,JDK为我们提供了工具类,它们的静态方法可以帮助我们直接对数组和List进行排序。

3.1 数组排序Arrays

Arrays的sort方法可以对已经实现了Comparable接口的进行排序,同时还可指定排序的范围。

//Arrays.sort对String进行排序
String[] strings = {"de", "dc", "aA", "As", "k", "b"};
Arrays.sort(strings);
assertTrue(Arrays.equals(strings,
        new String[]{"As", "aA", "b", "dc", "de", "k"}));

指定范围排序,需要注意的是,index是从0开始算的,包含fromIndex,不包含toIndex:

//Arrays.sort指定范围排序
strings = new String[]{"z", "a", "d", "b"};
Arrays.sort(strings, 0, 3);
assertTrue(Arrays.equals(strings,
        new String[]{"a", "d", "z", "b"}));

对于基本类型,一样可以进行排序,并不需要使用封装类:

//Arrays.sort对基本类型排序
int[] nums = {3, 1, 20, 2, 38, 2, 94};
Arrays.sort(nums);
assertTrue(Arrays.equals(nums,
        new int[]{1, 2, 2, 3, 20, 38, 94}));

还能多线程进行排序,其实是拆分成多个子数组再进行排序,最终再合并结果。

//Arrays.parallelSort多线程排序
nums = new int[]{3, 1, 20, 2, 38, 2, 94};
Arrays.parallelSort(nums);
assertTrue(Arrays.equals(nums,
        new int[]{1, 2, 2, 3, 20, 38, 94}));

对于没有实现Comparable的类也可以使用,但需要提供Comparator来指定比较策略。本文的没有实现Comparable接口的类Person如下:

import lombok.AllArgsConstructor;
import lombok.Data;

@Data
@AllArgsConstructor
public class Person {
    private String name;
    private int age;
}

排序:

//Arrays.sort提供Comparator进行排序
Person[] persons = new Person[]{
        new Person("Larry", 18),
        new Person("David", 30),
        new Person("James", 20),
        new Person("Harry", 18)};
Arrays.sort(persons, Comparator.comparingInt(Person::getAge));
assertTrue(Arrays.equals(persons, new Person[]{
        new Person("Larry", 18),
        new Person("Harry", 18),
        new Person("James", 20),
        new Person("David", 30)}));

3.2 List排序Collections

JDK的Collections工具类提供了排序方法,可以方便使用。对于实现Comparable的类进行排序:

//Collections.sort对于实现Comparable的类进行排序
List<String> names = asList("Larry", "Harry", "James", "David");
Collections.sort(names);
assertEquals(names, asList("David", "Harry", "James", "Larry"));

提供Comparator进行排序:

//Collections.sort提供Comparator进行排序
List<Person> persons2 = asList(
        new Person("Larry", 18),
        new Person("David", 30),
        new Person("James", 20),
        new Person("Harry", 18));
Collections.sort(persons2, Comparator.comparingInt(Person::getAge));
assertEquals(persons2, asList(
        new Person("Larry", 18),
        new Person("Harry", 18),
        new Person("James", 20),
        new Person("David", 30)));

反序:只是把List反过来,并不代表一定是按照大小顺序的:

//Collections.reverse反序
names = asList("Larry", "Harry", "James", "David");
Collections.reverse(names);
assertEquals(names, asList("David", "James", "Harry", "Larry"));

4 成员方法

4.1 List排序

List接口有sort(Comparator<? super E> c)方法,可以实现对自身的排序,会影响自身的顺序。

//List.sort排序
names = asList("Larry", "Harry", "James", "David");
names.sort(Comparator.naturalOrder());
assertEquals(names, asList("David", "Harry", "James", "Larry"));

4.2 Stream排序

Stream提供了sorted()和sorted(Comparator<? super T> comparator)进行排序,会返回一个新的Stream。

//Stream.sorted排序
names = asList("Larry", "Harry", "James", "David");
List<String> result = names.stream()
        .sorted()
        .collect(Collectors.toList());
assertEquals(result, asList("David", "Harry", "James", "Larry"));

//Stream.sorted提供Comparator排序
names = asList("Larry", "Harry", "James", "David");
result = names.stream()
        .sorted(Comparator.naturalOrder())
        .collect(Collectors.toList());
assertEquals(result, asList("David", "Harry", "James", "Larry"));

5 方便对象排序的Comparator

5.1 单字段排序

对类的单字段进行排序很简单,只要提供形如:

  • Comparator.comparing(类名::属性getter)

的Comparator就行了。如果需要倒序,就需要:

  • Comparator.comparing(类名::属性getter).reversed()
  • 或Comparator.comparing(类名::属性getter, Comparator.reverseOrder())。

具体代码使用(为了不破坏List的原有顺序,我们都使用Stream来操作):

//单字段排序-升序
List<Person> personList = asList(
        new Person("Larry", 18),
        new Person("David", 30),
        new Person("David", 3),
        new Person("James", 20),
        new Person("Harry", 18));
List<Person> personResult = personList.stream()
        .sorted(Comparator.comparing(Person::getName))
        .collect(Collectors.toList());
assertEquals(personResult, asList(
        new Person("David", 30),
        new Person("David", 3),
        new Person("Harry", 18),
        new Person("James", 20),
        new Person("Larry", 18)));

//单字段排序-倒序1
personResult = personList.stream()
        .sorted(Comparator.comparing(Person::getName).reversed())
        .collect(Collectors.toList());
assertEquals(personResult, asList(
        new Person("Larry", 18),
        new Person("James", 20),
        new Person("Harry", 18),
        new Person("David", 30),
        new Person("David", 3)));
//单字段排序-倒序2
personResult = personList.stream()
        .sorted(Comparator.comparing(Person::getName, Comparator.reverseOrder()))
        .collect(Collectors.toList());
assertEquals(personResult, asList(
        new Person("Larry", 18),
        new Person("James", 20),
        new Person("Harry", 18),
        new Person("David", 30),
        new Person("David", 3)));

5.2 多字段排序

多字段其实也很方便,只需要用thenComparing进行连接就可以:Comparator.comparing(类名::属性一getter).thenComparing(类名::属性二getter) 具体代码使用例子如下:

//多字段排序-1升2升
personResult = personList.stream()
        .sorted(Comparator.comparing(Person::getName)
                .thenComparing(Person::getAge))
        .collect(Collectors.toList());
assertEquals(personResult, asList(
        new Person("David", 3),
        new Person("David", 30),
        new Person("Harry", 18),
        new Person("James", 20),
        new Person("Larry", 18)));

//多字段排序-1升2倒
personResult = personList.stream()
        .sorted(Comparator.comparing(Person::getName)
                .thenComparing(Person::getAge, Comparator.reverseOrder()))
        .collect(Collectors.toList());
assertEquals(personResult, asList(
        new Person("David", 30),
        new Person("David", 3),
        new Person("Harry", 18),
        new Person("James", 20),
        new Person("Larry", 18)));

//多字段排序-1倒2升
personResult = personList.stream()
        .sorted(Comparator.comparing(Person::getName, Comparator.reverseOrder())
                .thenComparing(Person::getAge))
        .collect(Collectors.toList());
assertEquals(personResult, asList(
        new Person("Larry", 18),
        new Person("James", 20),
        new Person("Harry", 18),
        new Person("David", 3),
        new Person("David", 30)));

//多字段排序-1倒2倒
personResult = personList.stream()
        .sorted(Comparator.comparing(Person::getName, Comparator.reverseOrder())
                .thenComparing(Person::getAge, Comparator.reverseOrder()))
        .collect(Collectors.toList());
assertEquals(personResult, asList(
        new Person("Larry", 18),
        new Person("James", 20),
        new Person("Harry", 18),
        new Person("David", 30),
        new Person("David", 3)));

6 总结

本文从比较排序相关的两个接口(Comparable和Comparator)讲起,并以代码实例的形式,讲解了Array、List、Stream排序的方法,这应该可以覆盖大部分Java排序的使用场景。对于其它集合类如Set和Map,一样可以进行排序处理,可以将它们转化为Stream然后再进行排序。


Code for all: GitHub

欢迎关注微信公众号<南瓜慢说>,将持续为你更新...

file

Recommendations:
Cloud Native
Terraform
Container: Docker/Kubernetes
Spring Boot / Spring Cloud
Https
如何制定切实可行的计划并好好执行

  • Author 作者: LarryDpk 南瓜慢说
  • Link 链接: https://www.pkslow.com/archives/java-sorting
  • 版权声明: 本博客所有文章除特别声明外,不可转载!
# Terraform101 # English # Terraform # Middleware # config # Go # Private # Kubernetes # pkslow # Test # HTTPS # Redis # Docker # Mac # Plan # Stream # MongoDB # Spring # DevOps # JVM # String # Map # Set # List # Performance # Email # Springboot # JavaCollections # ArrayList # Java
Terraform101 English Terraform Middleware config Go Private Kubernetes pkslow Test HTTPS Redis Docker Mac Plan Stream MongoDB Spring DevOps JVM String Map Set List Performance Email Springboot JavaCollections ArrayList Java
Mac以树形结构显示目录
Oracle用decode函数或CASE-WHEN实现自定义排序
  • Contents
  • Site Overview
南瓜慢说

南瓜慢说

多年Java开发,主要专注后端技术:Java/Spring/Springboot/微服务/大数据等。

多读书,多分享;多写作,多整理。

241 Posts
9 Categories
30 Tags
RSS
0%
© 2020 — 2022 南瓜慢说 pkslow The WebSite keeping alive:   粤ICP备20036375号