ZetCode

Java Comparable & Comparator

最后修改于 2024 年 1 月 27 日

Java Comparable 和 Comparator 教程展示了如何使用 Comparable 和 Comparator 接口在 Java 中比较对象。比较两个对象在进行排序时至关重要。

当使用自定义 Java 对象执行比较时,我们可以使用 ComparableComparator 接口。

Java Comparable

Comparable 接口对每个实现它的类的对象强制执行总排序。此排序称为该类的自然排序。必须实现该类的 compareTo 方法以提供自然比较。

Java Comparator

Comparator 接口对某些对象集合强制执行总排序。可以将比较器传递给排序方法(例如 Collections.sortArrays.sort)以精确控制排序顺序。比较器还可用于控制某些数据结构(例如排序集或排序映射)的顺序,或者为没有自然排序的对象集合提供排序。

Comparable vs Comparator

以下两个列表总结了两个接口之间的差异。

Java Comparable

Java Comparator

Java 内置 Comparator 示例

Java 语言提供了一些内置的 Comparator。

com/zetcode/JavaBuiltInComparatorEx.java
package com.zetcode;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

public class JavaBuiltInComparatorEx {

    public static void main(String[] args) {

        List<String> words = new ArrayList<>();

        words.add("dog");
        words.add("pen");
        words.add("sky");
        words.add("rock");
        words.add("den");
        words.add("fountain");

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

        words.sort(Comparator.reverseOrder());
        words.forEach(System.out::println);
    }
}

在此示例中,我们按升序和降序对单词数组进行排序。

words.sort(Comparator.naturalOrder());

Comparator.naturalOrder 返回一个内置的自然顺序 Comparator

words.sort(Comparator.reverseOrder());

Comparator.reverseOrder 返回一个比较器,该比较器强制执行自然排序的逆序。

Comparator.comparingInt

Comparator.comparingInt 方法从提供的类型中提取 int 排序键,并按该键进行比较。

com/zetcode/JavaBuiltInComparatorEx2.java
package com.zetcode;

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

public class JavaBuiltInComparatorEx2 {

    public static void main(String[] args) {

        var p1 = new Person("Robert", LocalDate.of(2008, 8, 21));
        var p2 = new Person("Monika", LocalDate.of(2008, 10, 5));
        var p3 = new Person("Tom", LocalDate.of(1977, 11, 30));
        var p4 = new Person("Elisabeth", LocalDate.of(2004, 8, 30));

        var persons = List.of(p1, p2, p3, p4 );

        var sorted = persons.stream()
                .sorted(Comparator.comparingInt(Person::age));
        sorted.forEach(System.out::println);
    }

    record Person(String name, LocalDate dateOfBirth) {

        public int age() {

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

        @Override
        public String toString() {
            final StringBuilder sb = new StringBuilder("Person{");
            sb.append("name='").append(name()).append('\'');
            sb.append(", age=").append(age());
            sb.append('}');
            return sb.toString();
        }
    }
}

在此示例中,我们使用 Comparator.comparingInt 方法按年龄比较 Person 对象。

Person{name='Robert', age=12}
Person{name='Monika', age=12}
Person{name='Elisabeth', age=16}
Person{name='Tom', age=43}

这些对象按年龄排序。

多个 Comparators

使用 Comparator.thenComparing 方法,我们可以在对对象进行排序时使用多个比较器。

com/zetcode/JavaMultipleComparatorsEx.java
package com.zetcode;

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

// Comparing list of objects by multiple object fields

public class JavaMultipleComparatorsEx {

    public static void main(String[] args) {

        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::name)
                .thenComparing(Person::city)
                .thenComparing(Person::dateOfBirth));

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

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

    }
}

我们有一个 Person 对象列表。我们按姓名、城市,最后按年龄比较对象。

var sorted = persons.stream()
    .sorted(Comparator.comparing(Person::name)
    .thenComparing(Person::city)
    .thenComparing(Person::dateOfBirth));

Comparator.thenComparing 方法允许我们将多个比较器应用于排序操作。

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

Java 自定义 Comparator

在下一个示例中,我们创建一个自定义 Comparator

com/zetcode/JavaCustomComparator.java
package com.zetcode;

import java.util.Arrays;
import java.util.List;

public class JavaCustomComparatorEx {

    public static void main(String[] args) {

        List<String> words = Arrays.asList("pen", "blue", "atom", "to",
                "ecclesiastical", "abbey", "car", "ten", "desk", "slim",
                "journey", "forest", "landscape", "achievement", "Antarctica");

        words.sort((e1, e2) -> e1.length() - e2.length());

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

        words.sort((e1, e2) -> e2.length() - e1.length());

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

我们有一个单词列表。这次我们按长度比较单词。

words.sort((e1, e2) -> e1.length() - e2.length());

此自定义比较器用于按大小升序对单词进行排序。

words.sort((e1, e2) -> e2.length() - e1.length() );

在第二种情况下,单词按降序排序。

to
pen
car
ten
blue
atom
desk
slim
abbey
forest
journey
landscape
Antarctica
achievement
ecclesiastical
ecclesiastical
achievement
Antarctica
landscape
journey
forest
abbey
blue
atom
desk
slim
pen
car
ten
to

Java 自定义 Comparator II

在以下示例中,我们创建两个自定义比较器。

com/zetcode/JavaCustomComparatorEx2.java
package com.zetcode;

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

// Comparing objects with Comparator in array

public class JavaCustomComparatorEx2 {

    public static void main(String[] args) {

        Car[] cars = {
                new Car("Volvo", 23400), new Car("Mazda", 13700),
                new Car("Porsche", 353800), new Car("Skoda", 8900),
                new Car("Volkswagen", 19900)
        };

        System.out.println("Comparison by price:");

        Arrays.sort(cars, new CompareByPrice());

        for (Car car : cars) {

            System.out.println(car);
        }

        System.out.println();

        System.out.println("Comparison by name:");

        Arrays.sort(cars, new CompareByName());

        for (Car car : cars) {

            System.out.println(car);
        }
    }
}

record Car(String name, int price) {}

class CompareByPrice implements Comparator<Car> {

    @Override
    public int compare(Car c1, Car c2) {

        return c1.price() - c2.price();
    }
}

class CompareByName implements Comparator<Car> {

    @Override
    public int compare(Car c1, Car c2) {

        return c1.name().compareTo(c2.name());
    }
}

我们有一个 Car 对象数组。我们创建两个自定义比较器,以按名称和价格比较对象。

class CompareByPrice implements Comparator<Car> {

    @Override
    public int compare(Car c1, Car c2) {

        return c1.price() - c2.price();
    }
}

自定义 CompareByPrice 比较器实现了 Comparator 接口;迫使我们实现 compare 方法。我们的实现按价格比较汽车对象。

class CompareByName implements Comparator<Car> {

    @Override
    public int compare(Car c1, Car c2) {

        return c1.name().compareTo(c2.name());
    }
}

在第二种情况下,我们按名称比较汽车对象。

Comparison by price:
Car{name='Skoda', price=8900}
Car{name='Mazda', price=13700}
Car{name='Volkswagen', price=19900}
Car{name='Volvo', price=23400}
Car{name='Porsche', price=353800}

Comparison by name:
Car{name='Mazda', price=13700}
Car{name='Porsche', price=353800}
Car{name='Skoda', price=8900}
Car{name='Volkswagen', price=19900}
Car{name='Volvo', price=23400}

Java Comparable 示例

在以下示例中,我们使用 Comparable 比较对象。

com/zetcode/JavaComparableEx.java
package com.zetcode;

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

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();
    }
}

public class JavaComparableEx {

    public static void main(String[] args) {

        Card[] cards = {
                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)};

        Arrays.sort(cards);

        for (Card card: cards) {

            System.out.println(card);
        }
    }
}

我们有一个 Card 对象列表。每张卡片都有一个值并属于一个花色。我们实现了 Comparable 接口,以便为 Card 类的对象提供一些自然排序。

@Override
public int compareTo(Card o) {

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

我们实现了 compareTo 方法。我们首先按值比较卡片,然后按花色比较卡片。

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 Comparator - 语言参考

在本文中,我们展示了如何使用 ComparableComparator 在 Java 中比较对象。

作者

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

列出所有Java教程