Java Compartor and Comparable

595 阅读6分钟

介绍

在使用自定义类型或尝试比较不能直接比较的对象时,我们需要使用比较策略。 我们可以简单地构建一个比较策略,但是要使用ComparatorComparable接口

举例

我们以一个足球队为例(我们想按照球队在球员的排名来排列他们)

先创建一个简单的Player

public class Player {
    private int ranking;
    private String name;
    private int age;
     
    // constructor, getters, setters  
}

创建一个List存放多个Player, 并使用Collections.sort进行排序

    List<Player> footballTeam = new ArrayList<>();
    Player p1 = new Player(50, "Tom", 27);
    Player p2 = new Player(70, "Micale", 29);
    Player p3 = new Player(60, "Jerry", 28);
    
    footballTeam.add(p1);
    footballTeam.add(p2);
    footballTeam.add(p3);
  
    System.out.println("Before Sorting : " + footballTeam);
    Collections.sort(footballTeam);
    System.out.println("After Sorting : " + footballTeam);

如预期的那样,这里会出现编译时错误:

让我们看一下Collections.sort的源码:

    public static <T extends Comparable<? super T>> void sort(List<T> list) {
        list.sort(null);
    }
    
    public static <T> void sort(List<T> list, Comparator<? super T> c) {
        list.sort(c);
    }

我们可以发现,调用Collections.sotr()方法时,要么类型变量T 实现了Comparable接口,表示T是一种具有可比较能力的类,要么我们自己实现Compartor接口(可以理解为创建一种比较策略),并作为参数传入sort方法。

理解上面的结论后,不难发现为什么会出现上图中的报错,Player没有实现Comparable接口,表明Player不具备比较能力,而我们也没有传入Compartor(比较策略), 所以 Collections.sort()根本不知道按照什么规则去排序。

Comparable

Java中一般以able为结尾的都是接口(Runnable, Iterable, AutoCloseable...), Comparable的是一个接口,定义了将对象与相同类型的其他对象进行比较的策略, 我们称这种策略为自然排序

// 该接口只有一个抽象方法compareTo, 返回一个int值
public interface Comparable<T> {
    public int compareTo(T o)
}

因此,为了能够排序–我们必须通过实现Comparable接口将Player对象定义为可比较的对象:

public class Player implements Comparable<Player> {
    //...
    @Override
    public int compareTo(Player otherPlayer) {
        return (this.getRanking() - otherPlayer.getRanking());
    }
}

排序顺序由compareTo()方法的返回值确定( 0: 表示相等, 1:表示大于, -1: 表示小于)

最终排序如下:

Before Sorting : [{name='Tom'}, {name='Micale'}, {name='Jerry'}]
After Sorting : [{name='Tom'}, {name='Jerry'}, {name='Micale'}]

Compartor

Comparator接口定义了一个compare(arg1,arg2)方法,该方法具有两个需要被比较对象的参数,其工作原理类似于Comparable.compareTo()方法。

@FunctionalInterface
public interface Comparator<T> {
    int compare(T o1, T o2)
}

@FunctionalInterface表明这是一个函数式接口,可以配合Java 8 lambda使用, 本文后面也会提到

实现Compartor接口,并重写compare方法,就意味着创建了一种新的排序规则(比较器)

创建Compartor

创建一个使用Playerrank属性作为排序规则的 Compartor

public class PlayerRankingComparator implements Comparator<Player> {
    @Override
    public int compare(Player firstPlayer, Player secondPlayer) {
       return (firstPlayer.getRanking() - secondPlayer.getRanking());
    }
}

同理,可以创建使用Playerage属性作为排序规则的Compartor

public class PlayerAgeComparator implements Comparator<Player> {
    @Override
    public int compare(Player firstPlayer, Player secondPlayer) {
       return (firstPlayer.getAge() - secondPlayer.getAge());
    }
}

使用Compartor

为了演示这个概念,我们通过在Collections.sort方法中引入第二个参数来修改Player的排序规则

通过使用这种方法,我们可以覆盖自然排序规则:

PlayerRankingComparator playerComparator = new PlayerRankingComparator();
Collections.sort(footballTeam, playerComparator);

如果我们想要一个不同的排序顺序,我们只需要更改我们使用的比较器(Compartor)即可:

PlayerAgeComparator playerComparator = new PlayerAgeComparator();
Collections.sort(footballTeam, playerComparator);

Java 8 Compartors

Java 8通过使用lambda表达式和compare()静态工厂方法提供了定义比较器的新途径。

让我们看一下如何使用lambda表达式创建比较器的简单示例:

Comparator<Player> byRanking 
 = (Player player1, Player player2) -> player1.getRanking() - player2.getRanking();

IDEA会提示你,可以替换成 Comparator.comparingInt

Comparator<Player> byRanking = Comparator.comparingInt(Player::getRanking);

让我们来学习一下 Java 8中的Compartor

介绍

Java 8对Comparator接口进行了一些增强,其中包括一些静态函数,这些静态函数在提出集合的排序顺序时很有用。还可以通过Comparator接口有效地利用Java 8 lambda。

创建 Employee 类

public class Employee {
    String name;
    int age;
    double salary;
    long mobile;
 
    // constructors, getters & setters
}

测试数据

Employee e1 = new Employee("Tom", 25, 3000.0, 9922001);
Employee e2 = new Employee("Jerry", 22, 2000.0, 59240001);
Employee e3 = new Employee("Sun", 35, 4000.0, 3924401);
  
Employee[] employees = new Employee[] {e1, e2, e3};

Comparator.comparing(Function keyExtractor)

Comparator.comparing静态函数接受keyExtractor(从变量类型T中提取出指定的key)作为排序键, 然后返回一个按照该排序键作为排序规则的Compartor(比较器),

    public static <T, U extends Comparable<? super U>> Comparator<T> comparing(Function<? super T, ? extends U> keyExtractor)
    {
        Objects.requireNonNull(keyExtractor);
        return (Comparator<T> & Serializable)
            (c1, c2) -> keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2));
    }

为了了解这一点,让我们使用Employee中的name字段作为排序键,并将其方法引用(Employee::getName)作为Function类型的参数传递。

Comparator<Employee> employeeNameComparator = Comparator.comparing(Employee::getName);
Arrays.sort(employees, employeeNameComparator);
System.out.println("======>按name排序");
Arrays.stream(employees).forEach(System.out::println);

输出结果:

======>按name排序
Employee{name='Jerry', age=22, salary=2000.0, mobile=59240001}
Employee{name='Sun', age=35, salary=4000.0, mobile=3924401}
Employee{name='Tom', age=25, salary=3000.0, mobile=9922001}

Comparator.comparing(Function keyExtractor, Compartor keyCompartor)

Comparator.comparing还有一个选项可以通过提供用于为排序键创建自定义排序的Comparator(keyCompartor)来帮助重写排序键的自然排序

keyCompartor: 覆盖排序键的默认排序规则,也可以理解成 为排序键提供的全新的自然排序规则

    public static <T, U> Comparator<T> comparing(Function<? super T, ? extends U> keyExtractor, Comparator<? super U> keyComparator)
    {
        Objects.requireNonNull(keyExtractor);
        Objects.requireNonNull(keyComparator);
        return (Comparator<T> & Serializable) (c1, c2) -> keyComparator.compare(keyExtractor.apply(c1), keyExtractor.apply(c2));
    }

让我们修改上面的测试,通过提供一个Comparator以按降序对名称进行排序作为Comparator.comparing的第二个参数,从而覆盖按名称字段进行排序的自然排序

    // 按name排序, 并修改name的自然排序规则
    Comparator<Employee> employeeNameComparator2 = Comparator.comparing(Employee::getName, (k1, k2) -> {return k2.compareTo(k1);});
    Arrays.sort(employees, employeeNameComparator2);
    System.out.println("======>按name降序排序");
    Arrays.stream(employees).forEach(System.out::println);

输出结果:

======>按name降序排序
Employee{name='Tom', age=25, salary=3000.0, mobile=9922001}
Employee{name='Sun', age=35, salary=4000.0, mobile=3924401}
Employee{name='Jerry', age=22, salary=2000.0, mobile=59240001}

Compartable vs Compartor

定义类的默认排序规则(或叫做自然排序规则)时, 实现Compartable是一个不错的选择式。

但是,我们必须问自己,如果我们已经有了Comparable,为什么还要使用Comparator

原因有以下几种:

  • 有时,我们无法修改要对其对象进行排序的类的源代码,因此无法使用Comparable
  • 使用Compartor使我们避免向类中添加其他代码
  • 我们可以定义多种不同的比较策略(Compartor),这在使用Comparable时是不可能的