ZetCode

Java 排序列表

上次修改时间:2024 年 7 月 4 日

在本文中,我们将展示如何在 Java 中对列表进行排序。

排序

排序是将元素按有序序列排列。 多年来,人们开发了几种算法来对数据进行排序; 例如,归并排序、快速排序、选择排序或冒泡排序。(排序的另一种含义是分类:将具有相似属性的元素分组。)

排序的相反操作,即将元素序列重新排列成随机或无意义的顺序,称为洗牌。

数据可以按字母顺序或数字顺序排序。 排序键指定用于进行排序的标准。 可以按多个键对对象进行排序。 例如,在对用户进行排序时,可以使用用户的姓名作为主要排序键,将其薪水作为次要排序键。

排序顺序

标准顺序称为升序:a 到 z,0 到 9。 反向顺序称为降序:z 到 a,9 到 0。 对于日期和时间,升序表示较早的值排在较晚的值之前,例如 5/5/2020 将排在 11/11/2021 之前。

稳定排序

稳定排序是指保留相等元素的初始顺序的排序。 有些排序算法本质上是稳定的,有些则不稳定。 例如,归并排序和冒泡排序是稳定的排序算法。 另一方面,堆排序和快速排序是不稳定排序算法的示例。

考虑以下值:3715593。 稳定排序产生以下结果:1335579。 值 3 和 5 的排序被保留。 不稳定排序可能会产生以下结果:1335579

Java 内部使用稳定的排序算法。

Java 排序方法

在 Java 中,我们可以就地对列表进行排序,也可以返回一个新的排序列表。

default void sort(Comparator<? super E> c)

List.sort 方法根据指定的 Comparator 引起的顺序对列表进行排序。 该排序是稳定的。 该方法就地修改列表。

Stream<T> sorted(Comparator<? super T> comparator)

Stream.sorted 方法返回一个由该流的元素组成的流,并根据提供的 Comparator 进行排序。 对于有序流,排序是稳定的。 对于无序流,不保证稳定性。 该方法不会修改原始列表; 它返回一个新的排序流/列表。

Java 排序整数列表

在以下示例中,我们对整数列表进行排序。

Main.java
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;

void main() {

    List<Integer> vals = Arrays.asList(5, -4, 0, 2, -1, 4, 7, 6, 1, -1, 3, 8, -2);
    vals.sort(Comparator.naturalOrder());
    System.out.println(vals);

    vals.sort(Comparator.reverseOrder());
    System.out.println(vals);
}

整数按升序和降序排序。 数据是就地排序的; 也就是说,原始列表被修改。

$ java Main.java
[-4, -2, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8]
[8, 7, 6, 5, 4, 3, 2, 1, 0, -1, -1, -2, -4]

在下一个示例中,我们不修改原始数据源。

Main.java
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;

void main() {

    List<Integer> vals = Arrays.asList(5, -4, 0, 2, -1, 4, 7, 6, 1, -1, 3, 8, -2);

    System.out.println("Ascending order");

    var sorted1 = vals.stream().sorted().toList();
    System.out.println(sorted1);

    System.out.println("-------------------------------");

    System.out.println("Descending order");

    var sorted2 = vals.stream().sorted(Comparator.reverseOrder()).toList();
    System.out.println(sorted2);

    System.out.println("-------------------------------");

    System.out.println("Original order");
    System.out.println(vals);
}

我们使用 Stream.sorted 对整数进行排序。 原始数据源保持不变。

$ java Main.java 
Ascending order
[-4, -2, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8]
-------------------------------
Descending order
[8, 7, 6, 5, 4, 3, 2, 1, 0, -1, -1, -2, -4]
-------------------------------
Original order
[5, -4, 0, 2, -1, 4, 7, 6, 1, -1, 3, 8, -2]

Java 排序字符串列表

以下示例对字符串进行排序。

Main.java
import java.util.Comparator;
import java.util.List;

void main() {

    var words = List.of("sky", "cloud", "atom", "club", "carpet", "wood", "water",
            "silk", "bike", "falcon", "owl", "mars");

    var sorted = words.stream().sorted().toList();
    System.out.println(sorted);

    var sorted2 = words.stream().sorted(Comparator.reverseOrder()).toList();
    System.out.println(sorted2);
}

我们有一个单词列表。 我们使用 Stream.sorted 对它们进行排序。

$ java Main.java
[atom, bike, carpet, cloud, club, falcon, mars, owl, silk, sky, water, wood]
[wood, water, sky, silk, owl, mars, falcon, club, cloud, carpet, bike, atom]

Java 不区分大小写的列表排序

在以下示例中,我们将展示如何以不区分大小写的顺序对字符串进行排序。

Main.java
import java.util.Arrays;
import java.util.Comparator;

void main() {

    var words = Arrays.asList("world", "War", "abbot", "Caesar", "castle", "sky",
            "den", "forest", "ocean", "water", "falcon", "owl", "rain", "Earth");

    words.sort(Comparator.naturalOrder());
    System.out.println(words);

    words.sort(String::compareToIgnoreCase);
    System.out.println(words);
}

我们在自然顺序中就地对单词列表进行排序,然后在不考虑大小写的情况下进行排序。

$ java Main.java
[Caesar, Earth, War, abbot, castle, den, falcon, forest, ocean, owl, rain, sky, ...
[abbot, Caesar, castle, den, Earth, falcon, forest, ocean, owl, rain, sky, War, ...

Java 按姓氏对姓名列表进行排序

以下示例按姓氏对全名进行排序。

Main.java
import java.util.Arrays;
import java.util.Comparator;
import java.util.function.Function;

void main() {

    var names = Arrays.asList("John Doe", "Lucy Smith",
            "Benjamin Young", "Robert Brown", "Thomas Moore",
            "Linda Black", "Adam Smith", "Jane Smith");

    Function<String, String> fun = (String fullName) -> fullName.split("\s")[1];
    names.sort(Comparator.comparing(fun).reversed());

    System.out.println(names);
}

我们有一个姓名列表。 我们按姓氏对姓名进行排序,以相反的顺序。 默认情况下,它们将按名字排序,因为名字位于姓氏之前。

Function<String, String> fun = (String fullName) -> fullName.split("\s")[1];

我们创建一个 Function,它是一个键提取器。 它从字符串中提取姓氏。

names.sort(Comparator.comparing(fun).reversed());

我们将该函数传递给 Comparator.comparing 方法。

$ java Main.java
[Benjamin Young, Lucy Smith, Adam Smith, Jane Smith, Thomas Moore, John Doe, ...

Java 按字段排序列表

我们将按对象的字段对对象列表进行排序。

Main.java
import java.util.Arrays;
import java.util.Comparator;

void main() {

    var cars = Arrays.asList(new Car("Volvo", 23400),
            new Car("Mazda", 13700), new Car("Porsche", 353800),
            new Car("Skoda", 8900),  new Car("Volkswagen", 19900));

    cars.sort(Comparator.comparing(Car::price));
    System.out.println(cars);

    cars.sort(Comparator.comparing(Car::name));
    System.out.println(cars);
}

record Car(String name, int price) {}

我们有一个汽车列表。 我们按汽车的价格排序,然后按汽车的名称排序。

cars.sort(Comparator.comparing(Car::price));

我们将 price 方法的引用传递给 Comparator.comparing

$ java Main.java
[Car[name=Skoda, price=8900], Car[name=Mazda, price=13700], Car[name=Volkswagen, ...
[Car[name=Mazda, price=13700], Car[name=Porsche, price=353800], Car[name=Skoda, ...

Java 按多个字段排序列表

下一个示例展示了如何按多个字段对对象进行排序。

Main.java
import java.time.LocalDate;
import java.time.Period;
import java.util.Comparator;
import java.util.List;

void main() {

    var persons = List.of(
            new Person("Peter", LocalDate.of(1998, 5, 11), "New York"),
            new Person("Sarah", LocalDate.of(2008, 8, 21), "Las Vegas"),
            new Person("Lucy", LocalDate.of(1988, 12, 10), "Toronto"),
            new Person("Sarah", LocalDate.of(2000, 9, 19), "New York"),
            new Person("Tom", LocalDate.of(2004, 8, 30), "Toronto"),
            new Person("Robert", LocalDate.of(2008, 11, 1), "San Diego"),
            new Person("Lucy", LocalDate.of(2008, 10, 5), "Los Angeles"),
            new Person("Sam", LocalDate.of(1986, 6, 17), "Dallas"),
            new Person("Elisabeth", LocalDate.of(1985, 7, 12), "New York"),
            new Person("Ruth", LocalDate.of(1994, 4, 28), "New York"),
            new Person("Sarah", LocalDate.of(1977, 11, 30), "New York")
    );

    var sorted = persons.stream().sorted(Comparator.comparing(Person::age)
            .thenComparing(Person::name).reversed());

    sorted.forEach(System.out::println);
}


record Person(String name, LocalDate dateOfBirth, String city) {

    public int age() {

        return Period.between(dateOfBirth(), LocalDate.now()).getYears();
    }
}

人员列表按年龄排序,然后按姓名排序。

var sorted = persons.stream().sorted(Comparator.comparing(Person::age)
    .thenComparing(Person::name).reversed());

列表首先通过 Comparator.comparing 按年龄排序; 第二次排序通过 thenComparing 完成。

$ java Main.java
Person[name=Sarah, dateOfBirth=1977-11-30, city=New York]
Person[name=Elisabeth, dateOfBirth=1985-07-12, city=New York]
Person[name=Sam, dateOfBirth=1986-06-17, city=Dallas]
Person[name=Lucy, dateOfBirth=1988-12-10, city=Toronto]
Person[name=Ruth, dateOfBirth=1994-04-28, city=New York]
Person[name=Peter, dateOfBirth=1998-05-11, city=New York]
Person[name=Sarah, dateOfBirth=2000-09-19, city=New York]
Person[name=Tom, dateOfBirth=2004-08-30, city=Toronto]
Person[name=Sarah, dateOfBirth=2008-08-21, city=Las Vegas]
Person[name=Robert, dateOfBirth=2008-11-01, city=San Diego]
Person[name=Lucy, dateOfBirth=2008-10-05, city=Los Angeles]

Java 使用自定义比较器排序列表

我们通过定义外部比较器对象来对对象列表进行排序。

Main.java
import java.util.Comparator;
import java.util.List;

void main() {

    var cards = List.of(
            new Card(Rank.KING, Suit.DIAMONDS),
            new Card(Rank.FIVE, Suit.HEARTS),
            new Card(Rank.ACE, Suit.CLUBS),
            new Card(Rank.NINE, Suit.SPADES),
            new Card(Rank.JACK, Suit.SPADES),
            new Card(Rank.JACK, Suit.DIAMONDS));

    var sorted = cards.stream().sorted(CardComparator.build()).toList();
    sorted.forEach(System.out::println);
}

enum Rank {
    TWO,
    THREE,
    FOUR,
    FIVE,
    SIX,
    SEVEN,
    EIGHT,
    NINE,
    TEN,
    JACK,
    QUEEN,
    KING,
    ACE,
}

enum Suit {
    CLUBS,
    DIAMONDS,
    HEARTS,
    SPADES
}

record Card(Rank rank, Suit suit) {
}

static class CardComparator implements Comparator<Card> {

    static CardComparator build() {
        return new CardComparator();
    }

    @Override
    public int compare(Card o1, Card o2) {
        return Comparator.comparing(Card::rank)
                .thenComparing(Card::suit)
                .compare(o1, o2);
    }
}

一副牌首先按等级排序,如果等级相同,则按花色排序。

static class CardComparator implements Comparator<Card> {

    static CardComparator build() {
        return new CardComparator();
    }

    @Override
    public int compare(Card o1, Card o2) {
        return Comparator.comparing(Card::rank)
                .thenComparing(Card::suit)
                .compare(o1, o2);
    }
}

我们定义一个外部比较器,它实现 Comparator 接口并定义 compare 方法。

$ java Main.java
Card[rank=FIVE, suit=HEARTS]
Card[rank=NINE, suit=SPADES]
Card[rank=JACK, suit=DIAMONDS]
Card[rank=JACK, suit=SPADES]
Card[rank=KING, suit=DIAMONDS]
Card[rank=ACE, suit=CLUBS]

Java 使用 Comparable 对象排序列表

现在我们使用 Comparable 接口对对象进行排序。

Main.java
import java.util.Comparator;
import java.util.List;

class Card implements Comparable<Card> {

    @Override
    public int compareTo(Card o) {

        return Comparator.comparing(Card::getRank)
                .thenComparing(Card::getSuit)
                .compare(this, o);
    }

    public enum Suit {
        CLUBS,
        DIAMONDS,
        HEARTS,
        SPADES
    }

    public enum Rank {
        TWO,
        THREE,
        FOUR,
        FIVE,
        SIX,
        SEVEN,
        EIGHT,
        NINE,
        TEN,
        JACK,
        QUEEN,
        KING,
        ACE,
    }

    private Suit suit;
    private Rank rank;

    public Card(Rank rank, Suit suit) {

        this.rank = rank;
        this.suit = suit;
    }

    public Rank getRank() {
        return rank;
    }

    public Suit getSuit() {
        return suit;
    }

    public void showCard() {

        rank = getRank();
        suit = getSuit();

        System.out.println(rank + " of " + suit);
    }

    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder("Card{");
        sb.append("suit=").append(suit);
        sb.append(", rank=").append(rank);
        sb.append('}');
        return sb.toString();
    }
}

void main() {

    var cards = List.of(
            new Card(Card.Rank.KING, Card.Suit.DIAMONDS),
            new Card(Card.Rank.FIVE, Card.Suit.HEARTS),
            new Card(Card.Rank.ACE, Card.Suit.CLUBS),
            new Card(Card.Rank.NINE, Card.Suit.SPADES),
            new Card(Card.Rank.JACK, Card.Suit.SPADES),
            new Card(Card.Rank.JACK, Card.Suit.DIAMONDS));

    var sorted = cards.stream().sorted().toList();
    sorted.forEach(System.out::println);
}

Comparable 接口定义了一个内部 compareTo 排序方法。

$ java Main.java 
Card{suit=HEARTS, rank=FIVE}
Card{suit=SPADES, rank=NINE}
Card{suit=DIAMONDS, rank=JACK}
Card{suit=SPADES, rank=JACK}
Card{suit=DIAMONDS, rank=KING}
Card{suit=CLUBS, rank=ACE}

Java 排序列表最终示例

最后一个示例总结了几种排序技术。

Main.java
import java.time.LocalDate;
import java.util.Comparator;
import java.util.function.Function;

void main() {

    var data = """
                John Doe, gardener, 1985-11-10
                Roger Roe, driver, 1998-09-11
                Lucia Smith, teacher, 1995-08-18
                Peter Black, programmer, 1997-04-04
                Roman Grant, programmer, 1987-07-14
                Michael Miller, programmer, 2002-05-14
            """;

    System.out.println("Sorted by date of birth");

    var users = data.lines().map(line -> line.trim().split(",\s?"))
            .map(parts -> new User(parts[0], parts[1], 
                LocalDate.parse(parts[2]))).toList();

    var sortedByDob = users.stream()
            .sorted(Comparator.comparing(User::dateOfBirth));
    sortedByDob.forEach(System.out::println);

    System.out.println("--------------------------------");

    System.out.println("Sorted by date of birth reversed");

    var sortedByDob2 = users.stream()
            .sorted(Comparator.comparing(User::dateOfBirth).reversed());
    sortedByDob2.forEach(System.out::println);

    System.out.println("--------------------------------");

    System.out.println("Sorted by last name");

    Function<User, String> byLastName = user -> user.name.split("\s")[1];

    var sortedByLastName = users.stream()
            .sorted(Comparator.comparing(byLastName));
    sortedByLastName.forEach(System.out::println);

    System.out.println("--------------------------------");

    System.out.println("Sorted by occupation");

    var sortedByOccupation = users.stream()
            .sorted(Comparator.comparing(User::occupation));

    sortedByOccupation.forEach(System.out::println);

    System.out.println("--------------------------------");

    System.out.println("Sorted by occupation and date of birth reversed");

    var sortedByOccupationAndDateOfBirth = users.stream()
            .sorted(Comparator.comparing(User::occupation)
                    .thenComparing(User::dateOfBirth).reversed());

    sortedByOccupationAndDateOfBirth.forEach(System.out::println);

}

record User(String name, String occupation, LocalDate dateOfBirth) {}

我们解析一个多行字符串以形成用户列表。 然后我们按出生日期、姓氏、职业以及职业和反向出生日期对列表进行排序。

$ java Main.java
Sorted by date of birth
User[name="John Doe, occupation=gardener, dateOfBirth=1985-11-10]
User[name=Roman Grant, occupation=programmer, dateOfBirth=1987-07-14]
User[name=Lucia Smith, occupation=teacher, dateOfBirth=1995-08-18]
User[name=Peter Black, occupation=programmer, dateOfBirth=1997-04-04]
User[name=Roger Roe, occupation=driver, dateOfBirth=1998-09-11]
User[name=Michael Miller, occupation=programmer, dateOfBirth=2002-05-14]
--------------------------------
Sorted by date of birth reversed
User[name=Michael Miller, occupation=programmer, dateOfBirth=2002-05-14]
User[name=Roger Roe, occupation=driver, dateOfBirth=1998-09-11]
User[name=Peter Black, occupation=programmer, dateOfBirth=1997-04-04]
User[name=Lucia Smith, occupation=teacher, dateOfBirth=1995-08-18]
User[name=Roman Grant, occupation=programmer, dateOfBirth=1987-07-14]
User[name="John Doe, occupation=gardener, dateOfBirth=1985-11-10]
--------------------------------
Sorted by last name
User[name=Peter Black, occupation=programmer, dateOfBirth=1997-04-04]
User[name="John Doe, occupation=gardener, dateOfBirth=1985-11-10]
User[name=Roman Grant, occupation=programmer, dateOfBirth=1987-07-14]
User[name=Michael Miller, occupation=programmer, dateOfBirth=2002-05-14]
User[name=Roger Roe, occupation=driver, dateOfBirth=1998-09-11]
User[name=Lucia Smith, occupation=teacher, dateOfBirth=1995-08-18]
--------------------------------
Sorted by occupation
User[name=Roger Roe, occupation=driver, dateOfBirth=1998-09-11]
User[name="John Doe, occupation=gardener, dateOfBirth=1985-11-10]
User[name=Peter Black, occupation=programmer, dateOfBirth=1997-04-04]
User[name=Roman Grant, occupation=programmer, dateOfBirth=1987-07-14]
User[name=Michael Miller, occupation=programmer, dateOfBirth=2002-05-14]
User[name=Lucia Smith, occupation=teacher, dateOfBirth=1995-08-18]
--------------------------------
Sorted by occupation and date of birth reversed
User[name=Lucia Smith, occupation=teacher, dateOfBirth=1995-08-18]
User[name=Michael Miller, occupation=programmer, dateOfBirth=2002-05-14]
User[name=Peter Black, occupation=programmer, dateOfBirth=1997-04-04]
User[name=Roman Grant, occupation=programmer, dateOfBirth=1987-07-14]
User[name="John Doe, occupation=gardener, dateOfBirth=1985-11-10]
User[name=Roger Roe, occupation=driver, dateOfBirth=1998-09-11]

来源

Java ArrayList - 语言参考

在本文中,我们对 Java 中的列表进行了排序。

作者

我的名字是 Jan Bodnar,我是一位充满热情的程序员,拥有丰富的编程经验。 自 2007 年以来,我一直在撰写编程文章。 迄今为止,我已经撰写了 1,400 多篇文章和 8 本电子书。 我拥有超过十年的编程教学经验。

列出所有Java教程