1. 程式人生 > >Java中Comparable和Comparator詳解

Java中Comparable和Comparator詳解

該文基於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)),會出現異常。