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) <= 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)>0 && y.compareTo(z)>0)</tt> implies * <tt>x.compareTo(z)>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)),會出現異常。