comparator接口_用Java中的Comparable和Comparator排序

可比还是可比?为您需要的排序算法选择正确的接口

bb8b740dff88ac0f29c488d9befdafbb.png

程序员经常需要将数据库中的元素排序到集合,数组或映射中。在Java中,我们可以实现任何类型的排序算法。使用Comparable接口和compareTo()方法,我们可以使用字母顺序,String长度,反向字母顺序或数字进行排序。该Comparator界面允许我们以更灵活的方式执行相同操作。

无论我们想做什么,我们只需要知道如何为给定的接口和类型实现正确的排序逻辑即可。

获取源代码

获取此Java Challenger 的代码(获取地址:https://github.com/rafadelnero/javaworld-challengers)。在遵循示例的同时,您可以运行自己的测试。

用自定义对象对Java列表进行排序

对于我们的示例,我们将使用到目前为止其他Java Challengers所使用的POJO。在第一个示例中,我们使用通用类型在类中实现Comparable接口:SimpsonSimpson

[ 在这个由12部分组成的综合课程中,从入门概念到高级设计模式学习Java!]

class Simpson implements Comparable {    String name;    Simpson(String name) {        this.name = name;    }    @Override    public int compareTo(Simpson simpson) {        return this.name.compareTo(simpson.name);    }}public class SimpsonSorting {     public static void main(String... sortingWithList) {        List simpsons = new ArrayList<>();        simpsons.add(new SimpsonCharacter("Homer "));        simpsons.add(new SimpsonCharacter("Marge "));        simpsons.add(new SimpsonCharacter("Bart "));        simpsons.add(new SimpsonCharacter("Lisa "));        Collections.sort(simpsons);        simpsons.stream().map(s -> s.name).forEach(System.out::print);        Collections.reverse(simpsons);        simpsons.stream().forEach(System.out::print);    }}

请注意,我们已经覆盖了compareTo()方法并传递了另一个Simpson对象。我们也重写了该toString()方法,只是为了使示例易于阅读。

该toString方法显示来自对象的所有信息。当我们打印对象时,输出将是中实现的toString()。

compareTo()方法

该compareTo()方法将给定对象或当前实例与指定对象进行比较,以确定对象的顺序。快速浏览一下compareTo()工作原理:

1e7161f014aaf152b969e86c3532bab0.png

我们只能使用与该sort()方法相当的类。如果我们尝试传递Simpson未实现的Comparable,则会收到编译错误。

该sort()方法通过传递任何对象来使用多态Comparable。然后将按预期对对象进行排序。

先前代码的输出为:

Bart Homer Lisa Marge 

如果我们想颠倒顺序,我们可以交换sort()的reverse(); 从:

Collections.sort(simpsons);

至:

Collections.reverse(simpsons);

部署该reverse()方法会将先前的输出更改为:

Marge Lisa Homer Bart 

排序Java数组

在Java中,我们可以对数组进行排序,只要它实现Comparable接口即可。这是一个例子:

public class ArraySorting {    public static void main(String... moeTavern) {        int[] moesPints = new int[] {9, 8, 7, 6, 1};        Arrays.sort(moesPints);        Arrays.stream(moesPints).forEach(System.out::print);        Simpson[] simpsons = new Simpson[]{new Simpson("Lisa"), new Simpson("Homer")};        Arrays.sort(simpsons);        Arrays.stream(simpsons).forEach(System.out::println);    }}

在第一次sort()调用中,数组被排序为:

1 6 7 8 9

在第二个sort()调用中,它被排序为:

Homer Lisa

请记住,自定义对象必须实现Comparable才能排序,即使是数组也是如此。

我可以对没有可比对象的对象进行排序吗?

如果Simpson对象未实现Comparable,则将引发ClassCastException。如果将其作为测试运行,您将看到类似以下输出的内容:

Error:(16, 20) java: no suitable method found for sort(java.util.List)    method java.util.Collections.sort(java.util.List) is not applicable      (inference variable T has incompatible bounds        equality constraints: com.javaworld.javachallengers.sortingcomparable.Simpson        lower bounds: java.lang.Comparable super T>)    method java.util.Collections.sort(java.util.List,java.util.Comparator super T>) is not applicable      (cannot infer type-variable(s) T        (actual and formal argument lists differ in length))

该日志可能令人困惑,但请不要担心。请记住,ClassCastException任何未实现该Comparable接口的已排序对象都将抛出a 。

使用TreeMap对地图排序

Java API包括许多有助于排序的类,包括TreeMap。在下面的示例中,我们用于TreeMap将键排序为Map。

public class TreeMapExample {    public static void main(String... barney) {        Map simpsonsCharacters = new TreeMap<>();        simpsonsCharacters.put(new SimpsonCharacter("Moe"), "shotgun");        simpsonsCharacters.put(new SimpsonCharacter("Lenny"), "Carl");        simpsonsCharacters.put(new SimpsonCharacter("Homer"), "television");        simpsonsCharacters.put(new SimpsonCharacter("Barney"), "beer");        System.out.println(simpsonsCharacters);    }}

TreeMap使用接口compareTo()实现的方法Comparable。结果中的每个元素Map均按其键排序。在这种情况下,输出为:

Barney=beer, Homer=television, Lenny=Carl, Moe=shotgun

但是请记住:如果对象未实现Comparable,ClassCastException则将抛出a。

使用TreeSet对集合进行排序

该Set接口负责存储唯一值,但是当我们使用TreeSet实现时,插入的元素将在我们添加它们时自动排序:

public class TreeSetExample {    public static void main(String... barney) {        Set simpsonsCharacters = new TreeSet<>();        simpsonsCharacters.add(new SimpsonCharacter("Moe"));        simpsonsCharacters.add(new SimpsonCharacter("Lenny"));        simpsonsCharacters.add(new SimpsonCharacter("Homer"));        simpsonsCharacters.add(new SimpsonCharacter("Barney"));        System.out.println(simpsonsCharacters);    }}

此代码的输出是:

Barney, Homer, Lenny, Moe

同样,如果我们使用的不是Comparable,ClassCastException则将抛出a。

用比较器排序

如果我们不想使用compareTo()POJO类中的相同方法怎么办?我们可以覆盖该Comparable方法以使用其他逻辑吗?下面是一个示例:

public class BadExampleOfComparable {    public static void main(String... args) {        List characters = new ArrayList<>();        SimpsonCharacter homer = new SimpsonCharacter("Homer") {            @Override            public int compareTo(SimpsonCharacter simpson) {                return this.name.length() - (simpson.name.length());            }        };        SimpsonCharacter moe = new SimpsonCharacter("Moe") {            @Override            public int compareTo(SimpsonCharacter simpson) {                return this.name.length() - (simpson.name.length());            }        };        characters.add(homer);        characters.add(moe);        Collections.sort(characters);        System.out.println(characters);    }}

如您所见,此代码很复杂,并且包含很多重复。compareTo()对于相同的逻辑,我们不得不重写该方法两次。如果还有更多元素,我们将不得不为每个对象复制逻辑。

幸运的是,我们具有Comparator接口,该接口使我们可以将compareTo()逻辑与Java类分离。考虑上面使用重写的同一示例Comparator:

public class GoodExampleOfComparator {    public static void main(String... args) {        List characters = new ArrayList<>();        SimpsonCharacter homer = new SimpsonCharacter("Homer");        SimpsonCharacter moe = new SimpsonCharacter("Moe");        characters.add(homer);        characters.add(moe);        Collections.sort(characters, (Comparator.                        comparingInt(character1 -> character1.name.length())                        .thenComparingInt(character2 -> character2.name.length())));        System.out.println(characters);    }}

这些示例说明了Comparable和之间的主要区别Comparator。

使用Comparable时,有你的对象单一,默认的比较。使用Comparator时,你需要要解决现有的compareTo(),或者当你需要使用特定的逻辑更灵活的方式。Comparator将排序逻辑与对象分离,并将逻辑包含compareTo()在sort()方法中。

将Comparator与匿名内部类一起使用

在下一个示例中,我们使用匿名内部类比较对象的值。一个匿名内部类,在这种情况下,任何类,它实现Comparator。使用它意味着我们不必实例化实现接口的命名类。相反,我们compareTo()在匿名内部类内部实现该方法。

public class MarvelComparator {    public static void main(String... comparator) {        List marvelHeroes = new ArrayList<>();        marvelHeroes.add("SpiderMan ");        marvelHeroes.add("Wolverine ");        marvelHeroes.add("Xavier ");        marvelHeroes.add("Cyclops ");        Collections.sort(marvelHeroes, new Comparator() {            @Override            public int compare(String hero1, String hero2) {                return hero1.compareTo(hero2);            }        });        Collections.sort(marvelHeroes, (m1, m2) -> m1.compareTo(m2));        Collections.sort(marvelHeroes, Comparator.naturalOrder());        marvelHeroes.forEach(System.out::print);    }}

有关内部类的更多信息

一个匿名内部类是单纯的任何类,它的名称并不重要,并实现我们宣布的接口。因此,在示例中,新Comparator的实际上是一个没有名称的类的实例化,该类使用所需的逻辑来实现该方法。

将Comparator与lambda表达式一起使用

匿名内部类非常冗长,这可能会导致我们的代码出现问题。在Comparator界面中,我们可以使用lambda表达式简化并简化代码。例如,我们可以更改此:

Collections.sort(marvel, new Comparator() {            @Override            public int compare(String hero1, String hero2) {                return hero1.compareTo(hero2);            }        });

对此:

Collections.sort(marvel, (m1, m2) -> m1.compareTo(m2));

更少的代码和相同的结果!

该代码的输出为:

Cyclops SpiderMan Wolverine Xavier 

通过更改此代码,我们可以使代码更简单:

Collections.sort(marvel, (m1, m2) -> m1.compareTo(m2));

对此:

Collections.sort(marvel, Comparator.naturalOrder());

核心Java类是否可比?

许多核心Java类和对象都实现了该Comparable接口,这意味着我们不必compareTo()为这些类实现逻辑。以下是一些熟悉的示例:

public final class String    implements java.io.Serializable, Comparable, CharSequence { ...
整数
public final class Integer extends Number implements Comparable { …
public final class Double extends Number implements Comparable {...

还有很多。我鼓励您探索Java核心类,以学习它们的重要模式和概念。

接受可比接口挑战!

通过弄清楚以下代码的输出来测试您学到了什么。请记住,如果仅通过学习自己解决挑战,就会学得最好。找到答案后,您可以检查以下答案。您也可以运行自己的测试以完全吸收这些概念。

public class SortComparableChallenge {    public static void main(String... doYourBest) {        Set set = new TreeSet<>();        set.add(new Simpson("Homer"));        set.add(new Simpson("Marge"));        set.add(new Simpson("Lisa"));        set.add(new Simpson("Bart"));        set.add(new Simpson("Maggie"));        List list = new ArrayList<>();        list.addAll(set);        Collections.reverse(list);        list.forEach(System.out::println);    }    static class Simpson implements Comparable {        String name;        public Simpson(String name) {            this.name = name;        }        public int compareTo(Simpson simpson) {            return simpson.name.compareTo(this.name);        }        public String toString() {            return this.name;        }    }}
该代码的输出是什么?
A)   Bart       Homer       Lisa       Maggie       MargeB)   Maggie       Bart       Lisa       Marge       HomerC)   Marge       Maggie       Lisa       Homer       BartD)   Indeterminate

TreeSet和reverse()

查看代码,您应该注意到的第一件事是我们正在使用TreeSet,因此元素将自动排序。第二件事是比较的顺序颠倒了,所以排序将以相反的顺序进行。

当我们第一次将元素添加到列表中时,会TreeSet自动将它们排序为:

MargeMaggieLisaHomerBart

然后,我们使用该reverse()方法,该方法可以反转元素的顺序。因此,最终输出将是:

BartHomerLisaMaggieMarge

可比性的常见错误

  • 尝试对方法中的不可比较对象进行排序sort()。
  • 使用Comparable同一对象中不同的排序策略。
  • 反转compareTo()方法中的比较,以便排序将采用相反的顺序,例如:正常顺序: public int compareTo(Simpson simpson) { this.name.compareTo(simpson.name); }倒序: public int compareTo(Simpson simpson) { simpson.name.compareTo(this.name); }
  • Comparable在TreeSet或TreeMap对象中添加不可比较的(任何未实现的对象)对象。

使用Java排序时要记住的几点

  • Comparable当比较是给定类的标准时使用。
  • Comparator需要更大灵活性时使用。
  • 可以将lambdas与一起使用Comparator。
  • 许多Java核心类都实现了Comparable。
  • 使用TreeMap或TreeSet排序时,一个Map或一个Set。
  • 该compareTo()方法适用于Comparable和Comparator。
  • compareTo()如果一个对象大于另一个对象,则该方法将返回一个正数,如果一个对象小于另一个,则返回负数,如果它们相同则返回零。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
JavaComparableComparator都是用于进行对象比较的接口。它们的用法如下: 1. Comparable接口 Comparable接口Java内置的接口,它包含一个方法compareTo(),用于比较对象的大小。实现该接口的类可以直接进行排序。 例如,我们定义一个Person类实现Comparable接口: ``` public class Person implements Comparable<Person> { private String name; private int age; public Person(String name, int age) { this.name = name; this.age = age; } public int compareTo(Person person) { // 按照年龄进行排序 return this.age - person.age; } } ``` 在这个例子,我们通过实现Comparable接口,并重写compareTo()方法,按照年龄进行排序。 使用Comparable接口进行排序的例子: ``` List<Person> list = new ArrayList<Person>(); list.add(new Person("Tom", 20)); list.add(new Person("Jerry", 18)); list.add(new Person("Jack", 25)); Collections.sort(list); for(Person p : list) { System.out.println(p.getName() + " " + p.getAge()); } ``` 输出结果: ``` Jerry 18 Tom 20 Jack 25 ``` 2. Comparator接口 Comparator接口也是Java内置的接口,它包含一个方法compare(),用于比较两个对象的大小。实现该接口的类可以定制不同的比较规则。 例如,我们定义一个PersonComparator类实现Comparator接口: ``` public class PersonComparator implements Comparator<Person> { public int compare(Person p1, Person p2) { // 按照姓名进行排序 return p1.getName().compareTo(p2.getName()); } } ``` 在这个例子,我们通过实现Comparator接口,并重写compare()方法,按照姓名进行排序。 使用Comparator接口进行排序的例子: ``` List<Person> list = new ArrayList<Person>(); list.add(new Person("Tom", 20)); list.add(new Person("Jerry", 18)); list.add(new Person("Jack", 25)); Collections.sort(list, new PersonComparator()); for(Person p : list) { System.out.println(p.getName() + " " + p.getAge()); } ``` 输出结果: ``` Jack 25 Jerry 18 Tom 20 ``` 总之,ComparableComparator都是用于对象比较的接口。使用Comparable接口可以方便地对实现该接口的对象进行排序,而使用Comparator接口可以定制不同的比较规则。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值