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

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

获取源代码

获取此Java Challenger 的代码 。 在遵循示例的同时,您可以运行自己的测试。

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

在我们的示例中,我们将使用到目前为止与其他Java Challenger相同的POJO。 在第一个示例中,我们使用通用类型的SimpsonSimpson类中实现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);
    }

}


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

0

this.name == simpson.name

<= -1

this.name < simpson.name

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

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

先前代码的输出为:

Bart Homer Lisa Marge 


Bart Homer Lisa Marge

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

Collections.sort(simpsons);


Collections.sort(simpsons);

至:

Collections.reverse(simpsons);


Collections.reverse(simpsons);

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

Marge Lisa Homer Bart 


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);
    }
}


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


1 6 7 8 9

在第二次sort()调用中,将其排序为:

Homer Lisa


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))


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);
    }
}


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


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);
    }
}


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


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);
    }

}


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);
    }
}


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);
    }
}

这些示例说明了ComparableComparator之间的主要区别。

当对象有一个默认的默认比较时,请使用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);
    }
}


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, new Comparator<String>() {
            @Override
            public int compare(String hero1, String hero2) {
                return hero1.compareTo(hero2);
            }
        });

对此:

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


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

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

该代码的输出为:

Cyclops SpiderMan Wolverine Xavier 


Cyclops SpiderMan Wolverine Xavier

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

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


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

对此:

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


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 String
    implements java.io.Serializable, Comparable<String>, CharSequence { ...

整数

public final class Integer extends Number implements Comparable<Integer> { …


public final class Integer extends Number implements Comparable<Integer> { …

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


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;
        }
    }
}


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


A)   Bart
       Homer
       Lisa
       Maggie
       Marge

B)   Maggie
       Bart
       Lisa
       Marge
       Homer

C)   Marge
       Maggie
       Lisa
       Homer
       Bart

D)   Indeterminate