天天看點

Java(31):資料結構排序---對接口 Comparable<T> 與 接口 Comparator<T> 的差別了解

日期:2017/11/19

     接口 Comparable<T> 與 接口 Comparator<T> 的差別

一、接口 Comparable 

java.lang  接口 Comparable

類型參數:

​T​

​ - 可以與此對象進行比較的那些對象的類型

所有已知子接口:

​​Delayed​​, 

​​Name​​, 

​​RunnableScheduledFuture​​<V>, 

​​ScheduledFuture​​<V>

所有已知實作類:

​​Authenticator.RequestorType​​, 

​​BigDecimal​​, 

​​BigInteger​​, 

​​Boolean​​, 

​​Byte​​, 

​​ByteBuffer​​, 

​​Calendar​​, 

​​Character​​, 

​​CharBuffer​​, 

​​Charset​​, 

​​ClientInfoStatus​​, 

​​CollationKey​​, 

​​Component.BaselineResizeBehavior​​, 

​​CompositeName​​, 

​​CompoundName​​, 

​​Date​​, 

​​Date​​, 

​​Desktop.Action​​, 

​​Diagnostic.Kind​​, 

​​Dialog.ModalExclusionType​​, 

​​Dialog.ModalityType​​, 

​​Double​​, 

​​DoubleBuffer​​, 

​​DropMode​​, 

​​ElementKind​​, 

​​ElementType​​, 

​​Enum​​, 

​​File​​, 

​​Float​​, 

​​FloatBuffer​​, 

​​Formatter.BigDecimalLayoutForm​​, 

​​FormSubmitEvent.MethodType​​, 

​​GregorianCalendar​​, 

​​GroupLayout.Alignment​​, 

​​IntBuffer​​, 

​​Integer​​, 

​​JavaFileObject.Kind​​, 

​​JTable.PrintMode​​, 

​​KeyRep.Type​​, 

​​LayoutStyle.ComponentPlacement​​, 

​​LdapName​​, 

​​Long​​, 

​​LongBuffer​​, 

​​MappedByteBuffer​​, 

​​MemoryType​​, 

​​MessageContext.Scope​​, 

​​Modifier​​, 

​​MultipleGradientPaint.ColorSpaceType​​, 

​​MultipleGradientPaint.CycleMethod​​, 

​​NestingKind​​, 

​​Normalizer.Form​​, 

​​ObjectName​​, 

​​ObjectStreamField​​, 

​​Proxy.Type​​, 

​​Rdn​​, 

​​Resource.AuthenticationType​​, 

​​RetentionPolicy​​, 

​​RoundingMode​​, 

​​RowFilter.ComparisonType​​, 

​​RowIdLifetime​​, 

​​RowSorterEvent.Type​​, 

​​Service.Mode​​, 

​​Short​​, 

​​ShortBuffer​​, 

​​SOAPBinding.ParameterStyle​​, 

​​SOAPBinding.Style​​, 

​​SOAPBinding.Use​​, 

​​SortOrder​​, 

​​SourceVersion​​, 

​​SSLEngineResult.HandshakeStatus​​, 

​​SSLEngineResult.Status​​, 

​​StandardLocation​​, 

​​String​​, 

​​SwingWorker.StateValue​​, 

​​Thread.State​​, 

​​Time​​, 

​​Timestamp​​, 

​​TimeUnit​​, 

​​TrayIcon.MessageType​​, 

​​TypeKind​​, 

​​URI​​, 

​​UUID​​, 

​​WebParam.Mode​​, 

​​XmlAccessOrder​​, 

​​XmlAccessType​​, 

​​XmlNsForm​​

public interface Comparable<T>

     此接口強行對實作它的每個類的對象進行整體排序。這種排序被稱為類的自然排序,類的 compareTo 方法被稱為它的自然比較方法。

     實作此接口的對象清單(和數組)可以通過 ​​Collections.sort​​​(和 ​​Arrays.sort​​​)進行自動排序。實作此接口的對象可以用作​​有序映射​​​中的鍵或​​有序集合​​​中的元素,無需指定​​比較器​​。

     對于類 C 的每一個 e1 和 e2 來說,當且僅當 e1.compareTo(e2) == 0 與 e1.equals(e2) 具有相同的 boolean 值時,類 C的自然排序才叫做與 equals 一緻。注意,null 不是任何類的執行個體,即使 e.equals(null) 傳回 false,e.compareTo(null) 也将抛出 NullPointerException。

     建議(雖然不是必需的)最好使自然排序與 equals 一緻。這是因為在使用自然排序與 equals 不一緻的元素(或鍵)時,沒有顯式比較器的有序集合(和有序映射表)行為表現“怪異”。尤其是,這樣的有序集合(或有序映射表)違背了根據 equals

     例如,如果将兩個鍵 a 和 b 添加到沒有使用顯式比較器的有序集合中,使 (!a.equals(b) && a.compareTo(b) == 0),那麼第二個 add 操作将傳回 false(有序集合的大小沒有增加),因為從有序集合的角度來看,a 和 b

     實際上,所有實作 Comparable 的 Java 核心類都具有與 equals 一緻的自然排序。java.math.BigDecimal 是個例外,它的自然排序将值相等但精确度不同的 BigDecimal

     從數學上講,定義給定類 C 上自然排序的關系式 如下:

{(x, y)|x.compareTo(y) <= 0}。

     整體排序的

 是:

{(x, y)|x.compareTo(y) == 0}。

     它直接遵循 

compareTo

 的協定,商是 

C

 的

等價關系

,自然排序是 

C

 的

整體排序

。當說到類的自然排序

與 equals 一緻

 時,是指自然排序的商是由類的 

​​equals(Object)​​

 方法定義的等價關系。

{(x, y)|x.equals(y)}。

     此接口是 ​​Java Collections Framework​​ 的成員。

方法摘要

​ int​

​compareTo(T​

​ 

          比較此對象與指定對象的順序。

方法詳細資訊

compareTo

int compareTo(​​T​​ o)

實作類必須確定對于所有的 x 和 y 都存在 sgn(x.compareTo(y)) == -sgn(y.compareTo(x)) 的關系。(這意味着如果 y.compareTo(x) 抛出一個異常,則 x.compareTo(y)

實作類還必須確定關系是可傳遞的:(x.compareTo(y)>0 && y.compareTo(z)>0) 意味着 x.compareTo(z)>0。

最後,實作者必須確定 x.compareTo(y)==0 意味着對于所有的 z,都存在 sgn(x.compareTo(z)) == sgn(y.compareTo(z))。 強烈推薦 (x.compareTo(y)==0) == (x.equals(y)) 這種做法,但并不是 嚴格要求這樣做。一般來說,任何實作 Comparable

在前面的描述中,符号 sgn(expression) 指定 signum 數學函數,該函數根據 expression 的值是負數、零還是正數,分别傳回 -1、0 或 1

參數:

​o​

​ - 要比較的對象。

傳回:

負整數、零或正整數,根據此對象是小于、等于還是大于指定對象。

抛出:

​ClassCastException​

​ - 如果指定對象的類型不允許它與此對象進行比較。

例子1:

package com.code.CompareInterface.ComparableInterface;

//類型 Person 必須實作繼承的抽象方法 Comparable<String>.compareTo(String)
public class Person implements Comparable<Person>{
  private String name;
  private Integer age;
  public Person(){    
  }
  public Person (String name ,Integer age){
    this.name = name;
    this.age = age;
  }
  public void setName(String name){
    this.name = name;
  }
  public String getName(){
    return name;
  }
  public void setAge(Integer age){
    this.age = age;
  }
  public Integer getAge(){
    return age;
  }

  @Override
  public int compareTo(Person p) {
    //auto 比較age
    if(this.age > p.age){
      return (this.age - p.age);
    }if(this.age < p.age){
      return (this.age - p.age);
    }    
    //auto 比較name
    if(this.name.compareTo(p.name) > 0){
      return 1;
    }
    if(this.name.compareTo(p.name) < 0){
      return -1;
    }
    return 0;
  }

}      
package com.code.CompareInterface.ComparableInterface;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.ListIterator;

public class ComparableTest {
  
  public static void main(String [] args){
    Person p1 = new Person("AA",19);
    Person p2 = new Person("aa",13);
    Person p3 = new Person("CC",13);
    Person p4 = new Person("DD",14);
    
    //不能執行個體化類型 List<Person>
    List<Person> list = new ArrayList<Person>();    
    list.add(p1);
    list.add(p2);
    list.add(p3);
    list.add(p4);
    ListIterator it = list.listIterator();
    System.out.println(list.size());
    while(it.hasNext()){
      Person s = new Person();
      s = (Person) it.next();
      System.out.println(s.getName()+ "---"+s.getAge());
    }
    System.out.println("------------------------");
    //回到正題,進行排序
    Collections.sort(list);    
    for(Person p : list){
      System.out.println(p.getName()+"---"+p.getAge());
    }
  
  }
  
  
}      

輸出結果:

4
AA---19
aa---13
CC---13
DD---14
------------------------
CC---13
aa---13
DD---14
AA---19      

解釋:(1)從Person.java 重寫的compareTo()可以看出先是比較age值大小,再以name的Unicode序号作為排序依據。

二、接口 Comparator 

java.util  接口 Comparator

類型參數:

​T​

​ - 此 Comparator 可以比較的對象類型

所有已知實作類:

​​Collator​​, 

​​RuleBasedCollator​​

public interface Comparator<T>

      強行對某個對象 collection 進行整體排序 的比較函數。可以将 Comparator 傳遞給 sort 方法(如 ​​Collections.sort​​​ 或 ​​Arrays.sort​​​),進而允許在排序順序上實作精确控制。還可以使用 Comparator 來控制某些資料結構(如​​有序 set​​​或​​有序映射​​​)的順序,或者為那些沒有​​自然順序​​的對象 collection 提供排序。

      當且僅當對于一組元素 S 中的每個 e1 和 e2 而言,c.compare(e1, e2)==0 與 e1.equals(e2) 具有相等的布爾值時,Comparator c 強行對 S 進行的排序才叫做與 equals 一緻 的排序。

      當使用具有與 equals 不一緻的強行排序能力的 Comparator 對有序 set(或有序映射)進行排序時,應該小心謹慎。假定一個帶顯式 Comparator c 的有序 set(或有序映射)與從 set S 中抽取出來的元素(或鍵)一起使用。如果 c 強行對 S 進行的排序是與 equals 不一緻的,那麼有序 set(或有序映射)将是行為“怪異的”。尤其是有序 set(或有序映射)将違背根據 equals

      例如,假定使用 Comparator ​

​c​

​​ 将滿足 ​

​(a.equals(b) && c.compare(a, b) != 0)​

​​ 的兩個元素 ​

​a​

​​ 和 ​

​b​

​​ 添加到一個空 ​

​TreeSet​

​​ 中,則第二個 ​

​add​

​​ 操作将傳回 true(樹 set 的大小将會增加),因為從樹 set 的角度來看,​

​a​

​​ 和 ​

​b​

​​ 是不相等的,即使這與 ​​Set.add​​ 方法的規範相反。

      注:通常來說,讓 Comparator 也實作 java.io.Serializable 是一個好主意,因為它們在可序列化的資料結構(像 ​​TreeSet​​​、​​TreeMap​​)中可用作排序方法。為了成功地序列化資料結構,Comparator(如果已提供)必須實作 Serializable。

      在算術上,定義給定 Comparator c 對給定對象 set S 實施強行排序 的關系式 為:

{(x, y) such that c.compare(x, y) <= 0}.

      此整體排序的

商 (quotient)

 為:

{(x, y) such that c.compare(x, y) == 0}.

      它直接遵循 

compare

 的協定,商是 

S

 上的

等價關系

,強行排序是 

S

 上的

整體排序

。當我們說 

c

 強行對 

S

 的排序是

與 equals 一緻

 的時,意思是說排序的商是對象的 

​​equals(Object)​​

 方法所定義的等價關系:

{(x, y) such that x.equals(y)}.

此接口是 ​​Java Collections Framework​​ 的成員。

方法摘要

​ int​

​compare(T o1, T​

​ 

          比較用來排序的兩個參數。

​ boolean​

​equals(Object​

​ 

          訓示某個其他對象是否“等于”此 Comparator。

方法詳細資訊

compare

int compare(​​T​​​ o1,

​​​T​​ o2)

在前面的描述中,符号 sgn(expression) 表示 signum 數學函數,根據 expression 的值為負數、0 還是正數,該函數分别傳回 -1、0 或 1。

實作程式必須確定對于所有的 x 和 y 而言,都存在 sgn(compare(x, y)) == -sgn(compare(y, x))。(這意味着當且僅當 compare(y, x) 抛出異常時 compare(x, y)

實作程式還必須確定關系是可傳遞的:((compare(x, y)>0) && (compare(y, z)>0)) 意味着 compare(x, z)>0。

最後,實作程式必須確定 compare(x, y)==0 意味着對于所有的 z 而言,都存在 sgn(compare(x, z))==sgn(compare(y, z))。

雖然這種情況很普遍,但并不 嚴格要求 (compare(x, y)==0) == (x.equals(y))。一般說來,任何違背這個條件的 Comparator 都應該清楚地指出這一事實。推薦的語言是“注意:此 Comparator 強行進行與 equals 不一緻的排序。”

參數:

​o1​

​ - 要比較的第一個對象。

​o2​

​ - 要比較的第二個對象。

傳回:

根據第一個參數小于、等于或大于第二個參數分别傳回負整數、零或正整數。

抛出:

​ClassCastException​

​ - 如果參數的類型不允許此 Comparator 對它們進行比較。

equals

boolean equals(​​Object​​ obj)

訓示某個其他對象是否“等于”此 Comparator。此方法必須遵守 

​​Object.equals(Object)​​ 的正常協定。此外,

僅當 指定的對象也是一個 Comparator,并且強行實施與此 Comparator 相同的排序時,此方法才傳回 

true。是以,

​comp1.equals(comp2)​

​ 意味着對于每個對象引用 

o1 和 

o2 而言,都存在 

sgn(comp1.compare(o1, o2))==sgn(comp2.compare(o1, o2))。

注意,不 重寫 Object.equals(Object) 方法總是 安全的。然而,在某些情況下,重寫此方法可以允許程式确定兩個不同的 Comparator 是否強行實施了相同的排序,進而提高性能。

覆寫:

類 

​Object​

​ 中的 

​equals​

參數:

​obj​

​ - 要進行比較的引用對象。

傳回:

僅當指定的對象也是一個 Comparator,并且強行實施與此 Comparator 相同的排序時才傳回 

​true​

​。

另請參見:

​​Object.equals(Object)​​, 

​​Object.hashCode()​​

例子2:

package com.code.CompareInterface.Comparator;

import java.util.Comparator;
//類型 Person 必須實作繼承的抽象方法 Comparator<Person>.compare(Person, Person)
public class Person implements Comparator<Person>{
  private String name;
  private Integer age;
  public Person(){    
  }
  public Person (String name ,Integer age){
    this.name = name;
    this.age = age;
  }
  public void setName(String name){
    this.name = name;
  }
  public String getName(){
    return name;
  }
  public void setAge(Integer age){
    this.age = age;
  }
  public Integer getAge(){
    return age;
  }
  @Override
  public int compare(Person p1, Person p2) {
    if(p1.getAge() > p2.getAge()){
      return 1;
    }else if(p1.getAge() < p2.getAge()){
      return -1;
    }else{
      if(p1.getName().compareTo(p2.getName()) > 0){
        return 1;
      }else if(p1.getName().compareTo(p2.getName()) > 0){
        return -1;
      }else{
        return 0;
      }
    }
  }

}      
package com.code.CompareInterface.Comparator;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.ListIterator;

import com.code.CompareInterface.ComparableInterface.Person;

public class ComparatorTest {

  public static void main(String[] args) {
    Person p1 = new Person("AA",19);
    Person p2 = new Person("aa",13);
    Person p3 = new Person("CC",13);
    Person p4 = new Person("DD",14);
    
    //不能執行個體化類型 List<Person>
    List<Person> list = new ArrayList<Person>();    
    list.add(p1);
    list.add(p2);
    list.add(p3);
    list.add(p4);
    ListIterator it = list.listIterator();
    System.out.println(list.size());
    while(it.hasNext()){
      Person s = new Person();
      s = (Person) it.next();
      System.out.println(s.getName()+ "---"+s.getAge());
    }
    System.out.println("------------------------");
    
    //差別與Compearable接口
    Collections.sort(list);
    for(Person p : list){
      System.out.println(p.getName()+"---"+p.getAge());
    }
  
  }

}      

輸出結果:

4
AA---19
aa---13
CC---13
DD---14
------------------------
CC---13
aa---13
DD---14
AA---19      

驗證了:使用兩種接口達到一樣的效果。

三、兩者差別