java使用Comparable或Comparator对对象进行排序

java Comparable和Comparator排序介绍

转载:
https://www.techiedelight.com/zh-tw/sort-list-of-objects-using-comparable-java/
https://www.techiedelight.com/zh-tw/sort-list-of-objects-using-comparator-java/

使用 Comparable 对对象进行排序

要在 Java 中对对象集合(使用某些属性)进行排序,我们可以使用 java.lang.Comparable 接口,它对实现它的每个类的对象施加自然排序。因此,如果一个对象实现 Comparable 接口,那么该对象的列表(和数组)可以使用 Collections.sort (和 Arrays.sort)进行自动排序。

实现此接口的对象也可以用作排序映射中的键(TreeMap) 或作为有序集合中的元素 (TreeSet),无需指定比较器。

此类的实现者需要重写抽象方法, compareTo() 定义在 java.util.Comparable,它将对象与指定对象进行比较,由 compareTo() 方法的返回值决定对象相对于指定对象的位置。

  • 如果 compareTo() 返回一个负整数,该对象小于指定对象。
  • 如果 compareTo() 返回一个零,该对象等于指定对象。
  • 如果 compareTo() 返回一个正整数,该对象大于指定对象。

以下是我们如何对列表进行排序, Employee 对象使用Java中的 Comparable 接口进行排序:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package com.example.demo.testdemo;

import java.util.*;

class Employee implements Comparable<Employee> {
    private String name;
    private int age;

    public Employee(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "{" + "name='" + name + '\'' + ", age=" + age + '}';
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    @Override
    public int compareTo(Employee o) {
        return o.getAge() - this.age;
    }
}

class Main {
    public static void main(String[] args) {
        List<Employee> employees =
            new ArrayList<>(Arrays.asList(new Employee("John", 15), new Employee("Sam", 20), new Employee("Joe", 10)));

        Collections.sort(employees);
        System.out.println(employees);
    }
}

输出:

1
[{name='Joe', age=10}, {name='John', age=15}, {name='Sam', age=20}]

上面的代码将仅按 age 排序,如果两个员工的年龄相同,那么他们在排序列表中的相对位置是不固定的,因此,最好使用多个字段比较对象以避免这种情況。

比较对象的多个字段

我们可以首先对员工列表使用 age 进行排序,然后使用 name 进行排序, 如下所示。现在对于年龄相同的员工,排序顺序由员工的姓名决定。

1
2
3
4
5
6
7
@Override
public int compareTo(Employee o) {
    if (this.age != o.getAge()) {
        return this.age - o.getAge();
    }
    return this.name.compareTo(o.getName());
}

注意,我们对int类型的aget进行比较,对于name属性,使用的是String对象内置的比较方法。该比较方法可以继续进一步包括其他属性。

完整代码如下所示:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import java.util.*;

class Employee implements Comparable<Employee> {
    private String name;
    private int age;

    public Employee(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "{" + "name='" + name + '\'' + ", age=" + age + '}';
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    @Override
    public int compareTo(Employee o) {
        if (this.age != o.getAge()) {
            return this.age - o.getAge();
        }
        return this.name.compareTo(o.getName());
    }
}

class Main {
    public static void main(String[] args) {
        List<Employee> employees = new ArrayList<>(Arrays.asList(new Employee("John", 15), new Employee("Sam", 20),
            new Employee("Dan", 20), new Employee("Will", 20), new Employee("Joe", 10)));

        Collections.sort(employees);
        System.out.println(employees);
    }
}

输出:

1
[{name='Joe', age=10}, {name='John', age=15}, {name='Dan', age=20}, {name='Sam', age=20}, {name='Will', age=20}]

Guava - ComparisonChain Class

我们可以使用 Guava 的 ComparisonChain 用于在 compareTo() 方法,如下:

1
2
3
4
5
6
7
@Override
public int compareTo(Employee o) {
    return ComparisonChain.start()
                .compare(this.age, o.getAge())
                .compare(this.name, o.getName())
                .result();
}

compareTo() 方法的返回值将与链中的地一个非零比较结果具有相同的符号,或者如果每个比较结果都为零,则该方法将为零。

注意, 只要其中一个返回非零结果,ComparisonChain 就会停止调用后续的 compareTo 和 compare 方法。

完整代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import com.google.common.collect.ComparisonChain;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

class Employee implements Comparable<Employee> {
    private String name;
    private int age;

    public Employee(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "{" + "name='" + name + '\'' + ", age=" + age + '}';
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    @Override
    public int compareTo(Employee o) {
        return ComparisonChain.start().compare(this.age, o.getAge()).compare(this.name, o.getName()).result();
    }
}

class Main {
    public static void main(String[] args) {
        List<Employee> employees = new ArrayList<>(Arrays.asList(new Employee("John", 15), new Employee("Sam", 20),
            new Employee("Dan", 20), new Employee("Will", 20), new Employee("Joe", 10)));

        Collections.sort(employees);
        System.out.println(employees);
    }
}

输出:

1
[{name='Joe', age=10}, {name='John', age=15}, {name='Dan', age=20}, {name='Sam', age=20}, {name='Will', age=20}]

Apache Commons

我们也可以使用 Apache Commons Lang 库的 CompareToBuilder 类来帮助实现 Comparable.compareTo(Object) 方法。要使用这个类,代码如下:

1
2
3
4
5
6
7
@Override
public int compareTo(Employee o) {
    return new CompareToBuilder()
                .append(this.age, o.getAge())
                .append(this.name, o.getName())
                .toComparison();
}

按照构造器的顺序进行比较,如果append方法返回非零结果,则该值将由 toComparison() 比较,并跳过后续的比较。

完整代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import org.apache.commons.lang3.builder.CompareToBuilder;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

class Employee implements Comparable<Employee> {
    private String name;
    private int age;

    public Employee(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "{" + "name='" + name + '\'' + ", age=" + age + '}';
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    @Override
    public int compareTo(Employee o) {
        return new CompareToBuilder().append(this.age, o.getAge()).append(this.name, o.getName()).toComparison();
    }
}

class Main {
    public static void main(String[] args) {
        List<Employee> employees = new ArrayList<>(Arrays.asList(new Employee("John", 15), new Employee("Sam", 20),
            new Employee("Dan", 20), new Employee("Will", 20), new Employee("Joe", 10)));

        Collections.sort(employees);
        System.out.println(employees);
    }
}

输出:

1
[{name='Joe', age=10}, {name='John', age=15}, {name='Dan', age=20}, {name='Sam', age=20}, {name='Will', age=20}]

使用 Comparator 对对象进行排序

Comparator 是一个接口,它为没有自然排序的对象集合提供排序。此类的实现者需要重写定义在 java.util.Comparator 的抽象方法 compare() 。由 compare() 方法返回的值决定地一个对象想对于第二个对象的位置。

  • 如果 compare() 返回一个负整数,第一个参数小于第二个。
  • 如果 compare() 返回零,第一个参数等于第二个。
  • 如果 compare() 返回一个正整数,第一个参数大于第二个。

在 Java 中有几种实现比较器的方法:

将 Comparator 作为参数传递给 sort() 方法

将 Comparator 比较器传递给排序方法(例如 Collections.sort、 Arrays.sort),允许精确控制排序顺序。在下面的例子中,我们得到一个 Comparator 比较 Person 对象的年龄。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import java.util.*;

class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "{" + "name='" + name + '\'' + ", age=" + age + '}';
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }
}

class Main {
    public static void main(String[] args) {
        List<Person> persons = new ArrayList<>(Arrays.asList(new Person("John", 15), new Person("Sam", 25),
            new Person("Will", 20), new Person("Dan", 20), new Person("Joe", 10)));

        Collections.sort(persons, new Comparator<Person>() {
            @Override
            public int compare(Person p1, Person p2) {
                return p1.getAge() - p2.getAge();
            }
        });

        System.out.println(persons);
    }
}

输出:

1
[{name='Joe', age=10}, {name='John', age=15}, {name='Will', age=20}, {name='Dan', age=20}, {name='Sam', age=25}]

因为 Comparator 是一个函数式接口,它可以用作 lambda 表达式或方法引用的赋值目标。所以,

1
2
3
4
5
6
Collections.sort(persons, new Comparator<Person>() {
    @Override
    public int compare(Person p1, Person p2) {
        return p1.getAge() - p2.getAge();
    }
});

可以改写为

1
Collections.sort(persons, (p1, p2) -> p1.getAge() - p2.getAge());

Java 8 对 Comparator 增强。现在 Comparator 有静态方法,比如 comparing(),它可以很容易地创建比较器来比较对象中的一些特定值。例如,要获得一个 Comparator 比较 Person 对象的年龄,我们可以这样做:

1
2
Comparator<Person> byAge = Comparator.comparing(Person::getAge);
Collections.sort(persons, byAge);

上面的代码将仅按 age 比较,如果两个人的年龄相同,那么他们在排序列表中的相对顺序是不固定的。因此,最好使用多个字段比较对象以避免这种情况。

比较对象的多个字段

可以先对 Persion 列表的 age 排序,然后对 name 排序,如下所示,对于相同年龄的人,排序由人名决定。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
Collections.sort(persons, new Comparator<Person>() {
    @Override
    public int compare(Person p1, Person p2)
    {
        if (p1.getAge() != p2.getAge()) {
            return p1.getAge() - p2.getAge();
        }
        return p1.getName().compareTo(p2.getName());
    }
});

注意,我们对int类型的aget进行比较,对于name属性,使用的是String对象内置的比较方法。该比较方法可以继续进一步包括其他属性。

可以使用 lambda 表达式来做到这一点,使用 .thenComparing() 方法,有效地将两种比较合二为一:

1
2
3
4
Comparator<Person> byAge = Comparator.comparing(Person::getAge);
Comparator<Person> byName = Comparator.comparing(Person::getName);
 
Collections.sort(persons, byAge.thenComparing(byName));

也可以使用 Guava 的 ComparisonChain 用于执行链式比较语句,如下所示:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
Collections.sort(persons, new Comparator<Person>() {
    @Override
    public int compare(Person p1, Person p2)
    {
        return ComparisonChain.start()
                        .compare(p1.getAge(), p2.getAge())
                        .compare(p1.getName(), p2.getName())
                        .result();
    }
});

compare() 方法的返回值,将与链中的地一个非零比较结果具有相同的符号,或者如果每个比较结果都为零,则该方法将为零。注意, 只要其中一个 compare 返回非零结果,ComparisonChain 停止调用后续的 compare 方法。

也可以使用 Apache Commons Lang 库的 CompareToBuilder 类来实现 Comparator.compare() 方法。代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
Collections.sort(persons, new Comparator<Person>() {
    @Override
    public int compare(Person p1, Person p2)
    {
        return new CompareToBuilder()
                        .append(p1.getAge(), p2.getAge())
                        .append(p1.getName(), p2.getName())
                        .toComparison();
    }
});

按照 CompareToBuilder 构造器的顺序进行比较,如果任何比较返回非零结果,则该值将由 toComparison() 处理,并跳过所有后续比较。

在单独的类中实现Comparator

可以在一个单独的类中实现 Comparator ,然后将该类的实例传送给 sort() 方法。如下所示:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import java.util.*;

class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "{" + "name='" + name + '\'' + ", age=" + age + '}';
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }
}

class MyComparator implements Comparator<Person> {
    @Override
    public int compare(Person p1, Person p2) {
        if (p1.getAge() != p2.getAge()) {
            return p1.getAge() - p2.getAge();
        }
        return p1.getName().compareTo(p2.getName());
    }
}

class Main {
    public static void main(String[] args) {
        List<Person> persons = new ArrayList<>(Arrays.asList(new Person("John", 15), new Person("Sam", 25),
            new Person("Will", 20), new Person("Dan", 20), new Person("Joe", 10)));

        Collections.sort(persons, new MyComparator());
        System.out.println(persons);
    }
}

输出:

1
[{name='Joe', age=10}, {name='John', age=15}, {name='Dan', age=20}, {name='Will', age=20}, {name='Sam', age=25}]

将比较器 Comparator 传给 List.sort() 方法

从 Java 8 开始, List 可以使用 sort() 方法根据指定的顺序对列表进行排序,如下所示:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import java.util.*;

class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "{" + "name='" + name + '\'' + ", age=" + age + '}';
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }
}

class Main {
    public static void main(String[] args) {
        List<Person> persons = new ArrayList<>(Arrays.asList(new Person("John", 15), new Person("Sam", 25),
            new Person("Will", 20), new Person("Dan", 20), new Person("Joe", 10)));

        persons.sort(Comparator.comparing(Person::getAge).thenComparing(Comparator.comparing(Person::getName)));

        System.out.println(persons);
    }
}

输出:

1
[{name='Joe', age=10}, {name='John', age=15}, {name='Dan', age=20}, {name='Will', age=20}, {name='Sam', age=25}]

将比较器 Comparator 传给 Stream.sorted() 方法

可以將Comparator 比较其传送给Stream 类的 sorted() 方法,它返回一个由该該流的元素组成的流,示例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;

class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "{" + "name='" + name + '\'' + ", age=" + age + '}';
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }
}

class Main {
    public static void main(String[] args) {
        List<Person> persons = new ArrayList<>(Arrays.asList(new Person("John", 15), new Person("Sam", 25),
            new Person("Will", 20), new Person("Dan", 20), new Person("Joe", 10)));

        persons = persons.stream()
            .sorted(Comparator.comparing(Person::getAge).thenComparing(Comparator.comparing(Person::getName)))
            .collect(Collectors.toList());

        System.out.println(persons);
    }
}

输出:

1
[{name='Joe', age=10}, {name='John', age=15}, {name='Dan', age=20}, {name='Will', age=20}, {name='Sam', age=25}]
Licensed under CC BY-NC-SA 4.0
Built with Hugo
主题 StackJimmy 设计