转载:
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}]
|