java compareto方法怎么排序的_深入理解Java中Comparable和Comparator排序

本文有牛旦教育原创,头条首发,转载注明来源。

如何为需要的排序算法选择正确的接口?通过本文的分析讲解,我们会找到答案参考答案。

程序员经常需要将数据库中的元素排序为集合、数组或映射。在Java中,我们可以实现任何类型的排序算法。使用Comparable接口和compareTo()方法,我们可以使用字母顺序、字符串长度、反字母顺序或数字进行排序。Comparator接口允许我们以更灵活的方式做同样的事情。

概括而言,无论我们想做什么,只需要知道如何为给定的接口和类型实现正确的排序逻辑就可以了

2900516f3da80d2aa5b628cf7439a19c.png

1.Comparable

1.1自定义对象列表排序

在这个示例中,我们使用实现了Comparable接口的POJO类,名为Figure,并在泛型类型中使用Figure:

public class Figure implements Comparable
{ String name ; Figure(String name) { this.name = name; } @Override public int compareTo(Figure figure) { return this.name.compareTo(figure.name); }@Override public String toString() { return name ; }}class FigureCharacter implements Comparable{ String name; FigureCharacter(String name) { this.name = name; } @Override public int compareTo(FigureCharacter fc) { return this.name.compareTo(fc.name); } @Override public String toString() { return name ; } }package com.nd.tutorial.lesson001;import java.util.ArrayList;import java.util.Collections;import java.util.List;public class FigureSorting { public static void main(String[] args) { List figures = new ArrayList<>(); figures.add(new FigureCharacter ("Nazha ")); figures.add(new FigureCharacter ("Wukong ")); figures.add(new FigureCharacter ("Guanyin ")); figures.add(new FigureCharacter ("Rulai ")); Collections.sort(figures); figures.stream().map(s -> s.name).forEach(System.out::print); Collections.reverse(figures); figures.stream().forEach(System.out::print); } }

注意,我们已经覆盖了compareTo()方法,并传入了另一个Figure对象。我们还覆盖了toString()方法,只是为了使示例更容易阅读。

toString方法显示了来自对象的信息。当我们打印对象时,输出为toString()中实现的任何内容。

1.2compareTo()方法

compareTo()方法将给定的对象或当前实例与指定的对象进行比较,以确定对象的顺序。下面快速看一下compareTo()是如何工作的:

2c5c522110e0729021d2031fbf89a4cd.png

我们对实现Comparable的类使用sort()方法排序。如果我们试图传递一个没有实现Comparable的Figure,我们将收到一个编译错误。

sort()方法通过传递任何可比较的对象来使用多态性。然后对象将按预期排序。

前面代码的输出为:

Guanyin Nazha Rulai Wukong

如果我们想反序显示,只要把Collections.sort(figures)换成Collections. reverse(figures)即可,显示结果如下:

Wukong Rulai Nazha Guanyin

1.3排序Java数组

在Java中,只要数组类型实现了Comparable接口,我们就可以用任何类型对数组进行排序。例如:

package com.nd.tutorial.lesson001;import java.util.Arrays;public class ArraySorting { public static void main(String[] args) {  int[] mps = new int[] { 9, 8, 7, 6, 1 }; Arrays.sort(mps); Arrays.stream(mps).forEach(System.out::print);  Figure[] figures = new Figure[] { new Figure("Nezha"), new Figure("Rulai") }; Arrays.sort(figures); System.out.println(); Arrays.stream(figures).forEach(System.out::println); }}

第一个sort()调用,数组排序成如下形式:

16789

第二个sort()调用,被排序成如下形式:

Nezha

Rulai

请记住,定制对象必须实现Comparable才能进行排序,即使作为数组也是如此。

1.4没有实现Comparable能排序吗?

如果Figure对象没有实现Comparable,IDE将提示语法错误,可以自己试试,类是如下::

Exception in thread "main" java.lang.Error: Unresolved compilation problem:

The method sort(List) in the type Collections is not applicable for the arguments (List)

at nd.tutorial/com.nd.tutorial.lesson001.FigureSorting.main(FigureSorting.java:15)

这个日志有点混乱,但是不用担心。只要记住,对于没有实现Comparable接口的任何排序对象,那样去排序都会出错。

1.5用TreeMap排序Map

Java API包含了许多用来辅助排序的类,包括TreeMap。在下面的示例中,我们使用TreeMap将键排序到Map中。

package com.nd.tutorial.lesson001;import java.util.Map;import java.util.TreeMap;public class TreeMapExample { public static void main(String[] args) { Map figureCharacter = new TreeMap<>(); figureCharacter.put(new FigureCharacter ("Nezha"), "Circle"); figureCharacter.put(new FigureCharacter ("Rulai"), "FiveMountain"); figureCharacter.put(new FigureCharacter ("Wukong"), "goldencudgel"); figureCharacter.put(new FigureCharacter ("Guanyin"), "Bottle"); System.out.println(figureCharacter); }}

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

{Guanyin=Bottle, Nezha=Circle, Rulai=FiveMountain, Wukong=goldencudgel}

但是请记住:如果对象没有实现Comparable,将错误。

1.6用TreeSet排序Set

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

package com.nd.tutorial.lesson001;import java.util.Set;import java.util.TreeSet;public class TreeSetExample { public static void main(String[] args) { Set figureCharacters = new TreeSet<>(); figureCharacters.add(new FigureCharacter ("Wukong")); figureCharacters.add(new FigureCharacter ("Guanyin")); figureCharacters.add(new FigureCharacter ("Nazha")); figureCharacters.add(new FigureCharacter ("Rulai")); System.out.println(figureCharacters); }}

代码输出结果为:

[Guanyin, Nazha, Rulai, Wukong]

同样,如果我们使用一个非Comparable的对象(没实现Comparable接口),将抛出一个错误。

2.用Comparator排序

如果我们不想使用POJO类中的相同compareTo()方法怎么办?我们是否可以覆盖Comparable方法来使用不同的逻辑呢?看下面例子:

 public class BadExampleOfComparable { public static void main(String[] args) { List characters = new ArrayList<>(); FigureCharacter guanyin = new FigureCharacter ("Guanyin") { @Override public int compareTo(FigureCharacter figure) { return this.name.length() - (figure.name.length()); } }; FigureCharacter nezha = new FigureCharacter ("Nazha") { @Override public int compareTo(FigureCharacter figure) { return this.name.length() - (figure.name.length()); } }; characters.add(guanyin); characters.add(nezha); Collections.sort(characters); System.out.println(characters); }}

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

幸运的是,我们有Comparator接口,它允许我们从Java类中分离compareTo()逻辑。使用Comparator重写上面的例子:

 public class GoodExampleOfComparator { public static void main(String[] args) { List characters = new ArrayList<>(); FigureCharacter guanyin = new FigureCharacter ("Guanyin"); FigureCharacter nezha = new FigureCharacter ("Nezha"); characters.add(guanyin); characters.add(nezha); Collections.sort(characters, (Comparator.< FigureCharacter > comparingInt(character1 -> character1.name.length()) .thenComparingInt(character2 -> character2.name.length()))); System.out.println(characters); }}

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

当使用Comparable时,对象只有一个默认比较。当您需要处理现有的compareTo()时,或者当您需要以更灵活的方式使用特定的逻辑时,请使用Comparator。Comparator从对象中分离排序逻辑,并在sort()方法中包含compareTo()逻辑。

2.1匿名内部类方式使用Comparator

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

 public class MarvelComparator { public static void main(String[] args) { 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); }}

关于匿名内部类:

匿名内部类就是任何名称无关紧要的类,它实现了我们声明的接口。在这个例子中,new Comparator实际上是一个没有名称的类的实例化,它用我们想要的逻辑实现了这个方法。

2.2lambda表达式方式用Comparator

匿名内部类比较冗长,这可能会导致代码中出现问题。在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());

3.核心Java类是Comparable的吗?

许多核心Java类和对象实现了Comparable接口,这意味着我们不必为这些类实现compareTo()逻辑。下面是一些常见的例子:

String

public final class String implements java.io.Serializable, Comparable, CharSequence { ...

Integer

public final class Integer extends Number implements Comparable { …

Double

public final class Double extends Number implements Comparable {...

还有很多其他的。推荐你去探索Java核心类,以了解它们的重要模式和概念。

接收Comparable的挑战

通过理解以下代码的输出来检验所学内容掌握如何。记住,如果你仅仅通过学习就能自己解决这个挑战,你会学得很好。也可运行下面程序进一步理解吸收这些概念。

 public class SortComparableChallenge { public static void main(String[] args) { Set
set = new TreeSet<>(); set.add(new Figure ("Honghaier")); set.add(new Figure ("Mowangniu")); set.add(new Figure ("Laoshujing")); set.add(new Figure ("Baibianjun")); set.add(new Figure ("Meixian")); List< Figure > list = new ArrayList<>(); list.addAll(set); Collections.reverse(list); list.forEach(System.out::println); } static class Figure implements Comparable< Figure > { String name; public Figure (String name) { this.name = name; } public int compareTo(Figure newday) { return newday.name.compareTo(this.name); } public String toString() { return this.name; } }}

上面程序代码的输出是哪一个:

A.

Baibianjun

Honghaier

Laoshujing

Meixian

Mowangniu

B.

Meixian

Baibianjun

Honghaier

Laoshujing

Mowangniu

C.

Mowangniu

Meixian

Laoshujing

Honghaier

Baibianjun

D.

不知道

TreeSet and reverse()

如代码所示,注意到的第一件事是我们使用了一个TreeSet,因此元素将自动排序。第二件事是比较的顺序是颠倒的,所以排序的顺序是颠倒的。

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

Mowangniu, Meixian, Laoshujing, Honghaier, Baibianjun

然后我们使用reverse()方法,它颠倒元素的顺序。所以最终的输出是:

Baibianjun

Honghaier

Laoshujing

Meixian

Mowangniu

使用Comparable常见错误

ü 试图在sort()方法中对不可比较的对象排序。

ü 在同一对象中对不同的排序策略使用Comparable。

ü 在compareTo()方法中反转比较,以便排序将按相反的顺序进行,如下所示:

正常排序

public int compareTo(Figure figure) {this.name.compareTo(figure.name);}

反转排序

public int compareTo(Simpson simpson) {simpson.name.compareTo(this.name);}

ü 在TreeSet或TreeMap对象中添加非可比对象(没实现Comparable的任何对象)。

小结

关于使用Java排序,需要记住的:

Ø 当比较是给定类的标准比较时,使用Comparable。

Ø 当您需要更多的灵活性时,使用Comparator。

Ø 可以将lambdas与Comparator一起使用。

Ø 许多Java核心类实现了Comparable。

Ø 对Map或Set排序时,使用TreeMap或TreeSet。

Ø compareTo()方法同时适用于Comparable和Comparator。

Ø 如果一个对象大于另一个对象,compareTo()方法将返回一个正数,如果小则返回一个负数,如果两个对象相同,则返回零。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值