天天看点

用Java中的Comparable和Comparator排序

程序员经常需要将数据库中的元素排序到集合,数组或映射中。 在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()

工作原理:

如果比较返回 然后 ...

>= 1

this.name > simpson.name

this.name == simpson.name

<= -1

this.name < simpson.name

我们只能使用与

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
                
翻译自: https://www.infoworld.com/article/3323403/java-challengers-5-sorting-with-comparable-and-comparator-in-java.html