Comparable和Comparator

  • Comparable和Comparator是什么?
  • 比较规则
  • 用法
  • 额外的比较器
  • 该使用哪个?
  • 比较与继承


Comparable和Comparator是什么?

  • Comparable为类提供了默认比较
  • Comparator可以为类提供额外的比较方式

比较规则

对于 int compareTo()int compare() 方法

  • 顺序排序:返回值 > 0
  • 逆序排序:返回值 < 0
  • 不排序:返回值 = 0
  • 返回值通常利用±1和0

重写要保证对于所有的x、y、z(sgn表示根据表达式的值返回-1、0、1)

  • 自反性:sgn[ x.compareTo(y) ] == -sgn[ y.compareTo(x) ]
  • 传递性:x.compareTo(y) > 0 && y.compareTo(z) > 0 && x.compareTo(z) > 0
  • 对称性:x.compareTo(y) == 0 && sgn[ x.compareTo(z) ] == sgn[ y.compareTo(z) ]
  • 此外建议满足 [ x.compareTo(y) == 0 ] == [ x.equals(y) ]

用法

比较应该从最关键的域开始,逐步进行到所有的域,若某个域的比较产生了非零的结果,则整个比较结束并返回该结果,如下

class Person implements Comparable<Person> {
    private String name;
    private int age;
    private int height;

    @Override
    public int compareTo(Person o) {
        int result = name.compareTo(o.name);
        if (result == 0) {
            result = Integer.compare(this.age, o.age);
            if (result == 0) {
                result = Integer.compare(this.height, o.height);
            }
        }
        return result;
    }
}

java8后Comparator提供了比较器构造方法,可简化上述比较过程

class Person implements Comparable<Person> {
    private String name;
    private int age;
    private int height;

    private static final Comparator<Person> COMPARATOR =
            Comparator.comparing((Person p) -> p.name)
                    .thenComparingInt(p -> p.age)
                    .thenComparingInt(p -> p.height);

    @Override
    public int compareTo(Person o) {
        return COMPARATOR.compare(this, o);
    }
}

Tips:

  • 继承Comparable应指定泛型限制比较对象及省去类型转换
  • 比较不建议使用 < 和 >
  • 对于基本数据类型,调用其包装类的compare方法
  • 对于引用数据类型,应递归调用其compareTo方法
  • Comparator的比较器构造方法是向下兼容的,如对于short也是用thenComparingInt

额外的比较器

如默认比较方式不符合要求,可通过Comparator定义额外的比较方式

class Person{
    int age;
    long height;
}

定义两个比较器,分别根据Age和Height进行比较,当需要比较时按需创建

class compareByAge implements Comparator<Person> {

    @Override
    public int compare(Person o1, Person o2) {
        return Integer.compare(o1.age, o2.age);
    }
}

class compareByHeight implements Comparator<Person> {

    @Override
    public int compare(Person o1, Person o2) {
        return Long.compare(o1.height, o2.height);
    }
}

该使用哪个?

  • 若是类库设计者或只有一种比较方式,应该为Bean提供默认比较(即实现Comparable),方便使用者直接使用
  • 若默认比较无法满足需求或要修改比较域,应该额外实现Comparator
  • 若存在多种比较方式,应该实现Comparator以创建不同的比较器

比较与继承

同equals()一样,compareTo()无法在继承时增加比较域又保持性质,而解决办法是把继承改为组合,并在子类中提供一个返回父类的公有视图(修改后将不再允许父类和子类比较)

class Person implements Comparable<Person> {
    private int age;

    public Person(int age) {
        this.age = age;
    }

    @Override
    public int compareTo(Person o) {
        return Integer.compare(this.age, o.age);
    }
}

class Man implements Comparable<Man> {
    private Person person;
    private int height;

    public Man(int age, int height) {
        person = new Person(age);
        this.height = height;
    }

    public Person asPerson() {
        return person;
    }

    @Override
    public int compareTo(Man o) {
        int result = this.person.compareTo(o.person);
        if (result == 0) {
            result = Integer.compare(this.height, o.height);
        }
        return result;
    }
}

其实比较在继承中是比较麻烦的,根据情况不同,需要判断传进来的是父类还是子类,比较的是父类的域还是子类新增的域,其中可能还涉及到多重继承,为避免这些复杂的情况,推荐使用上述的方式一劳永逸

如下记录几种别的方式(以后研究)

//使用父类域比较不重写compareTo
/*class Man extends Person{
    private int height;

    public Man(int age, int height) {
        super(age);
        this.height = height;
    }
}*/

//当传递父类时用父类域,传递子类时用父类+子类域
/*class Man extends Person implements Comparable<Man> {
    private int height;

    public Man(int age, int height) {
        super(age);
        this.height = height;
    }

    @Override
    public int compareTo(Person o) {
        return super.compareTo(o);
    }

    @Override
    public int compareTo(Man o) {
        int result = super.compareTo(o);
        if (result == 0) {
            result = Integer.compare(this.height, ((Man) o).height);
        }
        return result;
    }
}*/

//同上,不过没实现Comparable
/*class Man extends Person {
    private int height;

    public Man(int age, int height) {
        super(age);
        this.height = height;
    }

    @Override
    public int compareTo(Person o) {
        if (o.getClass() == Person.class) {
            return super.compareTo(o);
        }
        int result = super.compareTo(o);
        if (result == 0) {
            result = Integer.compare(this.height, ((Man) o).height);
        }
        return result;
    }
}*/