一、概述
在编程时,我们经常需要对对象集合进行排序。如果我们想要对多个字段上的对象进行排序,排序逻辑有时会变得难以实现。在本教程中,我们将讨论解决该问题的几种不同方法,以及它们的优缺点。
2. 示例Person
类
让我们定义一个Person
类,它有两个字段,name
和age
。在整个示例中,我们将首先根据name
比较Person
对象,然后根据age
比较:
public class Person { @Nonnull private String name; private int age; // constructor // getters and setters }
在这里,我们添加了一个@Nonnull
注释以保持示例简单。但是在生产代码中,我们可能需要处理可空字段的比较。
3.使用Comparator.compare()
Java 提供了Comparator
接口来比较两个相同类型的对象。我们可以使用自定义逻辑实现其compare(T o1, T o2)
方法以执行所需的比较。
3.1.不同的字段一一检查
让我们逐个比较字段:
public class CheckFieldsOneByOne implements Comparator<Person> { @Override public int compare(Person o1, Person o2) { int nameCompare = o1.getName().compareTo(o2.getName()); if(nameCompare != 0) { return nameCompare; } return Integer.compare(o1.getAge(), o2.getAge()); } }
在这里,我们使用String
类的compareTo()
方法和Integer
类的compare()
方法依次比较name
和age
字段。
这需要大量的输入,有时还需要处理许多特殊情况。因此,当我们有更多的字段要比较时,很难维护和扩展。通常,不建议在生产代码中使用此方法。
3.2.使用Guava 的ComparisonChain
链
首先,让我们将Google Guava 库依赖项添加到我们的pom.xml
中:
<dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>31.1-jre</version> </dependency>
我们可以使用这个库中的ComparisonChain
类来简化逻辑:
public class ComparisonChainExample implements Comparator<Person> { @Override public int compare(Person o1, Person o2) { return ComparisonChain.start() .compare(o1.getName(), o2.getName()) .compare(o1.getAge(), o2.getAge()) .result(); } }
在这里,我们分别使用ComparisonChain 中的compare(int left, int right)
和compare(Comparable<?> left, Comparable<?> right)
方法来ComparisonChain
name
和age
。
这种方法隐藏了比较细节,只暴露了我们关心的内容——我们想要比较的字段以及它们应该被比较的顺序。此外,我们应该注意,我们不需要任何额外的null
处理逻辑,因为库方法会处理它。因此,它变得更容易维护和扩展。
3.3.使用Apache Commons 的CompareToBuilder
进行排序
首先,让我们将Apache Commons 的依赖项添加到pom.xml
中:
<dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.12.0</version> </dependency>
与前面的示例类似,我们可以使用Apache Commons 中的CompareToBuilder
来减少所需的样板代码:
public class CompareToBuilderExample implements Comparator<Person> { @Override public int compare(Person o1, Person o2) { return new CompareToBuilder() .append(o1.getName(), o2.getName()) .append(o1.getAge(), o2.getAge()) .build(); } }
这种方法与Guava 的ComparisonChain
非常相似——它也隐藏了比较细节并且易于维护和扩展.
4. 使用Comparator.comparing()
和Lambda 表达式
从Java 8 开始,Comparator
接口中添加了几个static
方法,可以采用lambda 表达式来创建Comparator
对象。我们可以使用它的comparing()
方法来构造我们需要的Comparator
:
public static Comparator<Person> createPersonLambdaComparator() { return Comparator.comparing(Person::getName) .thenComparing(Person::getAge); }
这种方法更加简洁和可读,因为它直接采用Person
类的getter。
它还保留了我们之前看到的方法的可维护性和可扩展性特征。此外,与之前的方法中的立即评估相比,这里的getters 是惰性评估的。因此,它的性能更好,更适合需要大量大数据比较的对延迟敏感的系统。
此外,这种方法仅使用核心Java 类,不需要任何第三方库作为依赖项。总的来说,这是最推荐的方法。
5. 检查比较结果
让我们测试我们看到的四个比较器并检查它们的行为。所有这些比较器都可以以相同的方式调用并且应该产生相同的结果:
@Test public void testComparePersonsFirstNameThenAge() { Person person1 = new Person("John", 21); Person person2 = new Person("Tom", 20); // Another person named John Person person3 = new Person("John", 22); List<Comparator<Person>> comparators = Arrays.asList(new CheckFieldsOneByOne(), new ComparisonChainExample(), new CompareToBuilderExample(), createPersonLambdaComparator()); // All comparators should produce the same result for(Comparator<Person> comparator : comparators) { Assertions.assertIterableEquals( Arrays.asList(person1, person2, person3) .stream() .sorted(comparator) .collect(Collectors.toList()), Arrays.asList(person1, person3, person2)); } }
在这里,person1
与person3 同名(“John”)person3,
但更年轻(21 < 22),而person3的名字(“John”)在字典序上小于person2的名字(“Tom”)。所以,最终的顺序是person1
、person3
、person2
。
此外,我们应该注意,如果我们在类变量name
上没有@Nonnull
注释,我们需要添加额外的逻辑来处理所有方法中的null 情况,除了Apache Commons 的CompareToBuilder
(它具有原生null处理内置)。
六,结论
在本文中,我们学习了在对对象集合进行排序时在多个字段上进行比较的不同方法。
0 评论