Java中Comparable和Comparator详解

版权声明:原创文章,转载请注明出处! https://blog.csdn.net/L_15156024189/article/details/84196414

该文基于JDK1.8。

一、Comparable<T>

Comparable<T>源码如下:

package java.lang;
import java.util.*;

/**
 * This interface imposes a total ordering on the objects of each class that
 * implements it.  This ordering is referred to as the class's <i>natural
 * ordering</i>, and the class's <tt>compareTo</tt> method is referred to as
 * its <i>natural comparison method</i>.<p>
 *
 * Lists (and arrays) of objects that implement this interface can be sorted
 * automatically by {@link Collections#sort(List) Collections.sort} (and
 * {@link Arrays#sort(Object[]) Arrays.sort}).  Objects that implement this
 * interface can be used as keys in a {@linkplain SortedMap sorted map} or as
 * elements in a {@linkplain SortedSet sorted set}, without the need to
 * specify a {@linkplain Comparator comparator}.<p>
 *
 * The natural ordering for a class <tt>C</tt> is said to be <i>consistent
 * with equals</i> if and only if <tt>e1.compareTo(e2) == 0</tt> has
 * the same boolean value as <tt>e1.equals(e2)</tt> for every
 * <tt>e1</tt> and <tt>e2</tt> of class <tt>C</tt>.  Note that <tt>null</tt>
 * is not an instance of any class, and <tt>e.compareTo(null)</tt> should
 * throw a <tt>NullPointerException</tt> even though <tt>e.equals(null)</tt>
 * returns <tt>false</tt>.<p>
 *
 * It is strongly recommended (though not required) that natural orderings be
 * consistent with equals.  This is so because sorted sets (and sorted maps)
 * without explicit comparators behave "strangely" when they are used with
 * elements (or keys) whose natural ordering is inconsistent with equals.  In
 * particular, such a sorted set (or sorted map) violates the general contract
 * for set (or map), which is defined in terms of the <tt>equals</tt>
 * method.<p>
 *
 * For example, if one adds two keys <tt>a</tt> and <tt>b</tt> such that
 * {@code (!a.equals(b) && a.compareTo(b) == 0)} to a sorted
 * set that does not use an explicit comparator, the second <tt>add</tt>
 * operation returns false (and the size of the sorted set does not increase)
 * because <tt>a</tt> and <tt>b</tt> are equivalent from the sorted set's
 * perspective.<p>
 *
 * Virtually all Java core classes that implement <tt>Comparable</tt> have natural
 * orderings that are consistent with equals.  One exception is
 * <tt>java.math.BigDecimal</tt>, whose natural ordering equates
 * <tt>BigDecimal</tt> objects with equal values and different precisions
 * (such as 4.0 and 4.00).<p>
 *
 * For the mathematically inclined, the <i>relation</i> that defines
 * the natural ordering on a given class C is:<pre>
 *       {(x, y) such that x.compareTo(y) &lt;= 0}.
 * </pre> The <i>quotient</i> for this total order is: <pre>
 *       {(x, y) such that x.compareTo(y) == 0}.
 * </pre>
 *
 * It follows immediately from the contract for <tt>compareTo</tt> that the
 * quotient is an <i>equivalence relation</i> on <tt>C</tt>, and that the
 * natural ordering is a <i>total order</i> on <tt>C</tt>.  When we say that a
 * class's natural ordering is <i>consistent with equals</i>, we mean that the
 * quotient for the natural ordering is the equivalence relation defined by
 * the class's {@link Object#equals(Object) equals(Object)} method:<pre>
 *     {(x, y) such that x.equals(y)}. </pre><p>
 *
 * This interface is a member of the
 * <a href="{@docRoot}/../technotes/guides/collections/index.html">
 * Java Collections Framework</a>.
 *
 * @param <T> the type of objects that this object may be compared to
 *
 * @author  Josh Bloch
 * @see java.util.Comparator
 * @since 1.2
 */
public interface Comparable<T> {
    /**
     * Compares this object with the specified object for order.  Returns a
     * negative integer, zero, or a positive integer as this object is less
     * than, equal to, or greater than the specified object.
     *
     * <p>The implementor must ensure <tt>sgn(x.compareTo(y)) ==
     * -sgn(y.compareTo(x))</tt> for all <tt>x</tt> and <tt>y</tt>.  (This
     * implies that <tt>x.compareTo(y)</tt> must throw an exception iff
     * <tt>y.compareTo(x)</tt> throws an exception.)
     *
     * <p>The implementor must also ensure that the relation is transitive:
     * <tt>(x.compareTo(y)&gt;0 &amp;&amp; y.compareTo(z)&gt;0)</tt> implies
     * <tt>x.compareTo(z)&gt;0</tt>.
     *
     * <p>Finally, the implementor must ensure that <tt>x.compareTo(y)==0</tt>
     * implies that <tt>sgn(x.compareTo(z)) == sgn(y.compareTo(z))</tt>, for
     * all <tt>z</tt>.
     *
     * <p>It is strongly recommended, but <i>not</i> strictly required that
     * <tt>(x.compareTo(y)==0) == (x.equals(y))</tt>.  Generally speaking, any
     * class that implements the <tt>Comparable</tt> interface and violates
     * this condition should clearly indicate this fact.  The recommended
     * language is "Note: this class has a natural ordering that is
     * inconsistent with equals."
     *
     * <p>In the foregoing description, the notation
     * <tt>sgn(</tt><i>expression</i><tt>)</tt> designates the mathematical
     * <i>signum</i> function, which is defined to return one of <tt>-1</tt>,
     * <tt>0</tt>, or <tt>1</tt> according to whether the value of
     * <i>expression</i> is negative, zero or positive.
     *
     * @param   o the object to be compared.
     * @return  a negative integer, zero, or a positive integer as this object
     *          is less than, equal to, or greater than the specified object.
     *
     * @throws NullPointerException if the specified object is null
     * @throws ClassCastException if the specified object's type prevents it
     *         from being compared to this object.
     */
    public int compareTo(T o);
}

去掉注释部分,简化后如下:

public interface Comparable<T> {
    public int compareTo(T o);
}

1、public interface Comparable<T>

        Comparable是一个泛型接口,其实从该接口的命名上我们就可以看出,一般在Java中,会使用形容词对接口进行命名,比如Serializable,但也有很多例外。该接口会对它的每一个实现类的对象施加一个全序(total ordering),后面我们简称为序,这个全序被称为实现类的自然顺序(natural ordering),实现类中具体实现的compareTo方法被称为该类的自然比较方法(natural comparison method)。也就是该接口可以赋予它的实现类一个自然顺序,就像整数一样,会有个自然顺序,这里整数就可以看做是实现类,具体的整数可以看成是该类的某个对象;0,1,2,……,100,……,这个顺序就可以看成是整数的自然顺序。

关于该接口有如下几点说明:

(1)某个类C的自然顺序被称为与它的equals方法是一致的,是指对类C的所有实例(instance)x和y,x.compareTo(y)==0和x.equals(y)有相同的boolean值,false或者true。注意null不是任何类的实例。事实上x.compareTo(null)会抛NullPointerException空指针异常,当x不为null时,x.equals(null)会返回false,x为null时也会抛出NullPointerException异常。强烈建议类的compareTo方法实现与equals方法实现保持一致。例如:

package main.compare;

/**
 * Created by leboop on 2018/11/18.
 */
public class Person implements Comparable<Person> {
    private String name;
    private Integer age;

    public Person(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public int compareTo(Person person) {

        return this.age - person.age;
    }

}

Person类实现了Comparable接口,按照属性age定义了自然顺序。但并未对equals方法重写,直接继承了父类Object的equals方法。测试类如下:

package main.compare;


import java.util.*;

/**
 * Created by leboop on 2018/11/18.
 */
public class CompareTest {
    public static void main(String[] args) {
        Person p1 = new Person("zhangsan", 10);
        Person p2 = new Person("lisi", 10);
        Person p3 = new Person("wangwu", 10);
        SortedSet<Person> sortedSet=new TreeSet();
        boolean flag1=sortedSet.add(p1);
        boolean flag2=sortedSet.add(p2);
        boolean flag3=sortedSet.add(p3);
        System.out.println("flag1:"+flag1);
        System.out.println("flag2:"+flag2);
        System.out.println("flag3:"+flag3);
        System.out.println(sortedSet);

    }
}

该测试类定义了三个Person对象p1、p2和p3,按照equals方法判断,他们是三个对象,按照序来看,三个对象的序是一样的,当我们将他们加入到一个有排序的集合中,p2和p3会加入失败,测试类运行结果如下:

flag1:true
flag2:false
flag3:false
集合大小:1

(2)事实上,实现了Comparable的所有Java核心类的自然顺序都与equals方法是一致的,一个特例是java.math.BigDecimal。

package main.compare;


import java.math.BigDecimal;
import java.util.*;

/**
 * Created by leboop on 2018/11/18.
 */
public class CompareTest {
    public static void main(String[] args) {
        BigDecimal d1=new BigDecimal("1.0");
        BigDecimal d2=new BigDecimal("1.00");
        System.out.println("d1的精度:"+d1.scale());
        System.out.println("d2的精度:"+d2.scale());
        System.out.println("d1.equals(d2):"+d1.equals(d2));
        System.out.println("d1.compareTo(d2):"+d1.compareTo(d2));
    }
}

程序运行结果:

d1的精度:1
d2的精度:2
d1.equals(d2):false
d1.compareTo(d2):0

Process finished with exit code 0

对于BigDecimal类,equals会比较值和精度,compareTo比较值大小。

2、public int compareTo(T o)

        该方法用于比较当前对象(this object)和给定对象(specified object)的序,它的返回值负整数,零,正整数分别对应当前对象小于,等于和大于给定对象。compareTo在子类的具体实现有以下几点必须保证的:

(1)对所有的对象x和y满足sgn(x.compareTo(y))等于-sgn(y.compareTo(x)),要么x.compareTo(y)抛出异常当且仅当x.compareTo(y)抛异常。这里sgn(x)是一个数学符号函数,当x>0时,sgn(x)=1;当x=0时,sgn(x)=0;当x<0时,sgn(x)=-1;

(2)如果x.compareTo(y)大于0,并且y.compareTo(z)大于0,那么x.compareTo(z)一定大于0;

(3)如果x.compareTo(y)等于0,那么对所有的z满足sgn(x.compareTo(z))和sgn(x.compareTo(z))相等;

compareTo在子类的具体实现中有一点是强烈推荐的(但不是必须),就是(x.compareTo(y)==0) == (x.equals(y))。如果实现Comparable接口的类违反了这个条件,应当清晰的表明这个事实。推荐使用语言“Note: this class has a natural ordering that is inconsistent with equals.”,大致意思就是说该类的自然顺序与类的equals方法不一致。

当给定对象是null时,该方法会抛出NullPointerException异常,如果给定的对象类型不能和当前对象进行比较会排出ClassCastException。

Comparable总结:

(1)某个类实现了Comparable接口就赋予了一个自然顺序

(2)compareTo方法实现时需要与equals方法保持一致

二、Comparator<T>

源码:

package java.util;

import java.io.Serializable;
import java.util.function.Function;
import java.util.function.ToIntFunction;
import java.util.function.ToLongFunction;
import java.util.function.ToDoubleFunction;
import java.util.Comparators;

@FunctionalInterface
public interface Comparator<T> {

    int compare(T o1, T o2);

    boolean equals(Object obj);

    default Comparator<T> reversed() {
        return Collections.reverseOrder(this);
    }

    default Comparator<T> thenComparing(Comparator<? super T> other) {
        Objects.requireNonNull(other);
        return (Comparator<T> & Serializable) (c1, c2) -> {
            int res = compare(c1, c2);
            return (res != 0) ? res : other.compare(c1, c2);
        };
    }

    default <U> Comparator<T> thenComparing(
            Function<? super T, ? extends U> keyExtractor,
            Comparator<? super U> keyComparator)
    {
        return thenComparing(comparing(keyExtractor, keyComparator));
    }

    default <U extends Comparable<? super U>> Comparator<T> thenComparing(
            Function<? super T, ? extends U> keyExtractor)
    {
        return thenComparing(comparing(keyExtractor));
    }

    default Comparator<T> thenComparingInt(ToIntFunction<? super T> keyExtractor) {
        return thenComparing(comparingInt(keyExtractor));
    }

    default Comparator<T> thenComparingLong(ToLongFunction<? super T> keyExtractor) {
        return thenComparing(comparingLong(keyExtractor));
    }

    default Comparator<T> thenComparingDouble(ToDoubleFunction<? super T> keyExtractor) {
        return thenComparing(comparingDouble(keyExtractor));
    }

    public static <T extends Comparable<? super T>> Comparator<T> reverseOrder() {
        return Collections.reverseOrder();
    }

    @SuppressWarnings("unchecked")
    public static <T extends Comparable<? super T>> Comparator<T> naturalOrder() {
        return (Comparator<T>) Comparators.NaturalOrderComparator.INSTANCE;
    }

    public static <T> Comparator<T> nullsFirst(Comparator<? super T> comparator) {
        return new Comparators.NullComparator<>(true, comparator);
    }

    public static <T> Comparator<T> nullsLast(Comparator<? super T> comparator) {
        return new Comparators.NullComparator<>(false, comparator);
    }

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

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

    public static <T> Comparator<T> comparingInt(ToIntFunction<? super T> keyExtractor) {
        Objects.requireNonNull(keyExtractor);
        return (Comparator<T> & Serializable)
            (c1, c2) -> Integer.compare(keyExtractor.applyAsInt(c1), keyExtractor.applyAsInt(c2));
    }

    public static <T> Comparator<T> comparingLong(ToLongFunction<? super T> keyExtractor) {
        Objects.requireNonNull(keyExtractor);
        return (Comparator<T> & Serializable)
            (c1, c2) -> Long.compare(keyExtractor.applyAsLong(c1), keyExtractor.applyAsLong(c2));
    }

    public static<T> Comparator<T> comparingDouble(ToDoubleFunction<? super T> keyExtractor) {
        Objects.requireNonNull(keyExtractor);
        return (Comparator<T> & Serializable)
            (c1, c2) -> Double.compare(keyExtractor.applyAsDouble(c1), keyExtractor.applyAsDouble(c2));
    }
}

1、Comparator<T>是一个比较器接口,被比较的类不需要直接实现它,例如Person类如下:

package main.compare;

/**
 * Created by leboop on 2018/11/18.
 */
public class Person {
    private String name;
    private Integer age;

    public Person(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

现在想对Person的对象按照年龄进行降序排序,我们只需要定义一个Person类的降序比较器,如下:

package main.compare;

import java.util.Comparator;

/**
 * Created by leboop on 2018/11/18.
 */
public class DesComparator implements Comparator<Person> {
    @Override
    public int compare(Person o1, Person o2) {
        return o2.getAge()-o1.getAge();
    }
}

测试类如下:

package main.compare;


import java.math.BigDecimal;
import java.util.*;

/**
 * Created by leboop on 2018/11/18.
 */
public class CompareTest {
    public static void main(String[] args) {
        Person p1=new Person("zhangsan",10);
        Person p2=new Person("lisi",29);
        Person p3=new Person("wangwu",25);
        Person p4=new Person("mazi",34);
        List<Person> personList=new ArrayList<>();
        personList.add(p1);
        personList.add(p2);
        personList.add(p3);
        personList.add(p4);
        System.out.println("排序前:"+personList);
        Collections.sort(personList,new DesComparator());
        System.out.println("排序后:"+personList);
    }
}

运行结果如下:

排序前:[Person{name='zhangsan', age=10}, Person{name='lisi', age=29}, Person{name='wangwu', age=25}, Person{name='mazi', age=34}]
排序后:[Person{name='mazi', age=34}, Person{name='lisi', age=29}, Person{name='wangwu', age=25}, Person{name='zhangsan', age=10}]

Process finished with exit code 0

2、对Comparator的compare方法实现有和Comparable接口对compareTo方法实现同样的要求。

三、Comparable和Comparator区别

1、对于Comparable接口来书,被比较对象所属的类需要直接实现Comparable接口,实现该接口的类被赋予一个自然顺序,实现该接口的类的自然顺序只有一个,而Comparator是一个比较器接口,被比较对象所属的类不需要直接实现该接口,可以单独写一个比较器类实现该接口,作为比较对象的一个比较器,对于一个类来说,可以实现多个比较器。

2、Comparator可以选择对null进行比较,而Comparable不可以。主要是因为Comparator的比较对象是compare方法的参数,而Comparable的比较方法compareTo方法需要对象来调用,而对象为null时(null.compareTo(obj)),会出现异常。

猜你喜欢

转载自blog.csdn.net/L_15156024189/article/details/84196414