程序员经常需要将数据库中的元素排序到集合,数组或映射中。 在Java中,我们可以实现任何类型的排序算法。 使用Comparable
接口和compareTo()
方法,我们可以使用字母顺序, String
长度,反向字母顺序或数字进行排序。 Comparator
界面允许我们以更灵活的方式执行相同操作。
无论我们想做什么,我们只需要知道如何为给定的接口和类型实现正确的排序逻辑即可。
获取源代码
获取此Java Challenger 的代码 。 在遵循示例的同时,您可以运行自己的测试。
用自定义对象对Java列表进行排序
在我们的示例中,我们将使用到目前为止与其他Java Challenger相同的POJO。 在第一个示例中,我们使用通用类型的Simpson
在Simpson
类中实现Comparable接口:
class Simpson implements Comparable<Simpson> {
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<SimpsonCharacter> 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()
工作原理:
如果比较返回 | 然后 ... |
| |
| |
| |
我们只能使用与sort()
方法相当的sort()
。 如果我们尝试传递未实现Comparable
的Simpson
,则会收到编译错误。
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<com.javaworld.javachallengers.sortingcomparable.Simpson>)
method java.util.Collections.<T>sort(java.util.List<T>) 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.<T>sort(java.util.List<T>,java.util.Comparator<? super T>) is not applicable
(cannot infer type-variable(s) T
(actual and formal argument lists differ in length))
该日志可能令人困惑,但请不要担心。 请记住,任何未实现Comparable
接口的已排序对象都将引发ClassCastException
。
使用TreeMap对地图排序
Java API包括许多有助于排序的类,包括TreeMap 。 在下面的示例中,我们使用TreeMap
将键排序到Map
。
public class TreeMapExample {
public static void main(String... barney) {
Map<SimpsonCharacter, String> 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
使用Comparable
接口实现的compareTo()
方法。 生成的Map
中的每个元素均按其键排序。 在这种情况下,输出为:
Barney=beer, Homer=television, Lenny=Carl, Moe=shotgun
但是请记住:如果对象未实现Comparable
,则将抛出ClassCastException
。
使用TreeSet对集合进行排序
Set
接口负责存储唯一值,但是当我们使用TreeSet实现时,插入的元素将在添加它们时自动排序:
public class TreeSetExample {
public static void main(String... barney) {
Set<SimpsonCharacter> 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
。
用比较器排序
如果我们不想使用POJO类中的相同compareTo()
方法怎么办? 我们可以重写Comparable
方法以使用其他逻辑吗? 下面是一个示例:
public class BadExampleOfComparable {
public static void main(String... args) {
List<SimpsonCharacter> 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<SimpsonCharacter> characters = new ArrayList<>();
SimpsonCharacter homer = new SimpsonCharacter("Homer");
SimpsonCharacter moe = new SimpsonCharacter("Moe");
characters.add(homer);
characters.add(moe);
Collections.sort(characters, (Comparator.<SimpsonCharacter>
comparingInt(character1 -> character1.name.length())
.thenComparingInt(character2 -> character2.name.length())));
System.out.println(characters);
}
}
这些示例说明了Comparable
和Comparator
之间的主要区别。
当对象有一个默认的默认比较时,请使用Comparable
。 使用Comparator
时,你需要解决现有compareTo()
或者当你需要使用特定的逻辑更灵活的方式。 Comparator
从您的对象分离排序逻辑,并在sort()
方法内包含compareTo()
逻辑。
将Comparator与匿名内部类一起使用
在下一个示例中,我们使用匿名内部类比较对象的值。 在这种情况下, 匿名内部类是实现Comparator
任何类。 使用它意味着我们不必实例化实现接口的命名类。 相反,我们在匿名内部类中实现了compareTo()
方法。
public class MarvelComparator {
public static void main(String... comparator) {
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);
}
}
有关内部类的更多信息
匿名内部类就是名称无关紧要且实现了我们声明的接口的任何类。 因此,在该示例中,新的Comparator
实际上是一个没有名称的类的实例化,该类使用所需的逻辑来实现该方法。
将Comparator与lambda表达式一起使用
匿名内部类非常冗长,这可能会导致我们的代码出现问题。 在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());
Java中的Lambda表达式
了解有关Java中的lambda表达式和其他功能编程技术的更多信息。
核心Java类是否可比?
许多核心Java类和对象都实现Comparable
接口,这意味着我们不必为这些类实现compareTo()
逻辑。 以下是一些熟悉的示例:
串
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence { ...
整数
public final class Integer extends Number implements Comparable<Integer> { …
双
public final class Double extends Number implements Comparable<Double> {...
还有很多。 我鼓励您探索Java核心类,以学习它们的重要模式和概念。
接受可比接口挑战!
通过弄清楚以下代码的输出来测试您学到了什么。 请记住,如果仅通过学习自己解决挑战,您将学得最好。 找到答案后,您可以检查以下答案。 您也可以运行自己的测试以完全吸收这些概念。
public class SortComparableChallenge {
public static void main(String... doYourBest) {
Set<Simpson> 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<Simpson> list = new ArrayList<>();
list.addAll(set);
Collections.reverse(list);
list.forEach(System.out::println);
}
static class Simpson implements Comparable<Simpson> {
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
Marge
B) Maggie
Bart
Lisa
Marge
Homer
C) Marge
Maggie
Lisa
Homer
Bart
D) Indeterminate