java常见三种排序(深入理解Java中Comparable和Comparator排序)
java常见三种排序(深入理解Java中Comparable和Comparator排序)1.2compareTo()方法 toString方法显示了来自对象的信息。当我们打印对象时,输出为toString()中实现的任何内容。1.1自定义对象列表排序 在这个示例中,我们使用实现了Comparable接口的POJO类,名为Figure,并在泛型类型中使用Figure:public class Figure implements Comparable<Figure> { String name ; Figure(String name) { this.name = name; } @Override public int compareTo(Figure figure) { return this.name.compareTo(figure.name); } @Override public String toString() { retur
本文有牛旦教育原创,头条首发,转载注明来源。
如何为需要的排序算法选择正确的接口?通过本文的分析讲解,我们会找到答案参考答案。
程序员经常需要将数据库中的元素排序为集合、数组或映射。在Java中,我们可以实现任何类型的排序算法。使用Comparable接口和compareTo()方法,我们可以使用字母顺序、字符串长度、反字母顺序或数字进行排序。Comparator接口允许我们以更灵活的方式做同样的事情。
概括而言,无论我们想做什么,只需要知道如何为给定的接口和类型实现正确的排序逻辑就可以了
1.1自定义对象列表排序
在这个示例中,我们使用实现了Comparable接口的POJO类,名为Figure,并在泛型类型中使用Figure:
public class Figure implements Comparable<Figure> { 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<FigureCharacter>{ 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<FigureCharacter> 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()是如何工作的:
我们对实现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<T>) in the type Collections is not applicable for the arguments (List<FigureCharacter>)
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 String> 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<FigureCharacter> 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<FigureCharacter> 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<FigureCharacter> 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<String> marvelHeroes = new ArrayList<>(); marvelHeroes.add("SpiderMan "); marvelHeroes.add("Wolverine "); marvelHeroes.add("Xavier "); marvelHeroes.add("Cyclops "); Collections.sort(marvelHeroes new Comparator<String>() { @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<String>() { @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<String> CharSequence { ...
Integer
public final class Integer extends Number implements Comparable<Integer> { …
Double
public final class Double extends Number implements Comparable<Double> {...
还有很多其他的。推荐你去探索Java核心类,以了解它们的重要模式和概念。
接收Comparable的挑战通过理解以下代码的输出来检验所学内容掌握如何。记住,如果你仅仅通过学习就能自己解决这个挑战,你会学得很好。也可运行下面程序进一步理解吸收这些概念。
public class SortComparableChallenge { public static void main(String[] args) { Set<Figure> 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()方法将返回一个正数,如果小则返回一个负数,如果两个对象相同,则返回零。