comparator具體用法

先舉一個例子説明comparator的用法。假設武器庫裏有許多的槍,這些槍有兩個重要指標一個是長度(len),另一個是威力(pow),現在對這些槍有個評價標準是:在威力相同的情況下長度越短越好,威力不同的情況下威力越大越好,對這些槍進行排序。

分析: 題目中同時對兩個變量進行比較並且比較的方式還不一樣,pow是由大到小排序,而len是由小到大排序。如果利用現有的容器裏的比較方法是不能實現的,通過實現comparator接口就可以很容易的實現比較了。先實現上面的例子,然後再講解原因。

class Weapon {
    int len;
    int pow;
    public Weapon(int len, int pow) {       
        this.len = len;
        this.pow = pow;
    }
    //weapon 兩個值,一個len,一個pow    
}

① 上面是weapon類有兩個成員變量分別是len 和 pow。

class WeaponComparator implements Comparator<Weapon>
{
    @Override
    public int compare(Weapon o1, Weapon o2) {
        if(o1.pow<o2.pow)
            return 1;
        //返回1表示當    o1.pow<o2.pow時,o1會在o2的後面,o2會排在前面。後面會詳細解析為什麼。

        if(o1.pow==o2.pow)
        {
        //在pow相同的情況下,按len的大小從小到大排序
            if(o1.len>o2.len)
            {
                return 1;

            }
            if(o1.len==o2.len) {
                return 0;               
            }           
                return -1;              
        }       
        return -1;
    }   
}

②上面的比較器實現了comparator接口。且用len和pow兩個值定義了比較規則。

public class ComparatorTest {


    public static void main(String[] args) {

        int arr[][]={{1,2},{2,3},{1,3},{2,2},{3,4},{2,4}};
        Weapon weapons[]=new Weapon[arr.length];
        for(int i=0;i<arr.length;i++)
        {
            weapons[i]=new Weapon(arr[i][0], arr[i][1]);
        }
        //生成一個weapon數組
        WeaponComparator wComparator=new WeaponComparator();
        //定義一個比較器
        Arrays.sort(weapons,wComparator );
        //Arrays.sort(weapons,wComparator );對weapons進行排序
        for(int i=0;i<arr.length;i++)
        {
            System.out.println(weapons[i].pow+"    "+weapons[i].len);
        }       
    }
}

③上面定義一個weapon類型的數組,然後利用Arrays.sort傳入自定義的比較器和weapons數組進行排序。

//輸出
/*
weapon  len
 4       2
 4       3
 3       1
 3       2
 2       1
 2       2
*/

④由輸出可以看出按照pow從大到小排序,在pow相同的情況下按照 len 從小到大排序。

總結一下:comparator用法其實很簡單,只要實現comparator接口中的compare方法並且在裏面實現自己的比較邏輯,然後調用排序類進行排序。

深入理解comparator

public interface Comparator<T> {

    int compare(T o1, T o2);

    boolean equals(Object obj);

}

comparator是一個接口,裏面compare和equals方法,一般實現自己的比較邏輯時,只要重寫int compare(T o1, T o2);方法就可以了,compare有三個返回值,-1,0,1,在重寫的時候一定要注意,排序的時候會利用這三個值的。再來看看Arrays.sort( arr[] ,comparator )到底是怎麼實現的,為什麼能夠實現自定義的排序。下面是做了一定簡化的代碼:

public static <T> void sort(T[] a, Comparator<? super T> c) {
        T[] aux = a.clone();
        mergeSort(aux, a, 0, a.length, c);
    }

原來sort 方法調用了 mergeSort 合併排序。下面看看 mergeSort的實現。

private static void mergeSort(Object[] src,
                                  Object[] dest,
                                  int low, int high, int off,
                                  Comparator c) {
        int length = high - low;

        // 如果數組的長度小於7時就使用插入排序
        if (length < 7) {
            for (int i=low; i<high; i++)
                for (int j=i; j>low && c.compare(dest[j-1], dest[j])>0; j--)
                    swap(dest, j, j-1);
            return;
            //在插入排序中的判斷大小的方法原來就是自定義的comparator中的compare方法
            //c.compare(dest[j-1], dest[j])>0
        }

        //如果長度大於7就遞歸的對其左右進行排序
        int destLow  = low;
        int destHigh = high;
        low  += off;
        high += off;
        int mid = (low + high) >>> 1;
        mergeSort(dest, src, low, mid, -off, c);
        mergeSort(dest, src, mid, high, -off, c);

        // 如果排好序的左邊的最大元素小於排好序的右邊的最小元素則可以知道從low到high都已經排好序了
        //直接複製到src數組中就行了
        if (c.compare(src[mid-1], src[mid]) <= 0) {
           System.arraycopy(src, low, dest, destLow, length);
           return;
        }

        //否則還是利用自定義的comparator中的compare方法來比較大小,對將左右兩邊的數組進行合併排序
        for(int i = destLow, p = low, q = mid; i < destHigh; i++) {
            if (q >= high || p < mid && c.compare(src[p], src[q]) <= 0)
                dest[i] = src[p++];
            else
                dest[i] = src[q++];
        }
    }

下面是插入排序中的swap()方法。

private static void swap(Object[] x, int a, int b) {
        Object t = x[a];
        x[a] = x[b];
        x[b] = t;
    }

上面的代碼進行幾點説明:
①上面的MergeSort方法對平常的MergeSort方法進行了優化。
②如果數組的長度小於7時直接調用了插入排序,這樣速度更快,這裏説的數組長度包括遞歸時左右子數組的長度。
③每次對左右子數組排序後,先判斷左邊的數組是不是都比右邊的小,這個很好判斷上面只用了一句話: if (c.compare(src[mid-1], src[mid]) <= 0) 如果是的,直接複製到數組中就可以,不用再進行一次排序。這樣可以節約時間。

Comparable

下面用Comparable方式實現 weapon的排序。

//weapon直接實現了Comparable接口並且重寫了compareTo方法,比較邏輯在compareTo方法裏。
class Weapon implements Comparable<Weapon>
{
    int len;
    int pow;
    public Weapon(int len, int pow) {       
        this.len = len;
        this.pow = pow;
    }
    @Override
    public int compareTo(Weapon o) {
        if(this.pow<o.pow)
            return 1;
        if(this.pow==o.pow)
        {
            if(this.len>o.len)
            {
                return 1;
            }
            if(this.len==o.len) {
                return 0;               
            }           
                return -1;              
        }       
        return -1;

    }

再看看如何對weapon排序的。

//直接調用Arrays.sort(weapons);就可以完成對weapon排序。
public class ComparableTest {

    public static void main(String[] args) {
        int arr[][]={{1,2},{2,3},{1,3},{2,2},{3,4},{2,4}};
        Weapon weapons[]=new Weapon[arr.length];
        for(int i=0;i<arr.length;i++)
        {
            weapons[i]=new Weapon(arr[i][0], arr[i][1]);
        }
        Arrays.sort(weapons);
        for(int i=0;i<arr.length;i++)
        {
            System.out.println(weapons[i].pow+"    "+weapons[i].len);
        }   
    }

}

有上面代碼可知
①定義類時實現comparable接口,並且在compareTo中實現比較大小的邏輯。就可以調用Arrays.sort()來實現排序。也可以用Collections.sort()來實現排序。
②如果某個類實現了comparable接口那麼它的比較邏輯就確定了,不能更改了,比如説String中的compareTo()方法中實現了比較字符串的邏輯,那麼對字符串數組進行比較大小時會調用這個比較方法,如果要改變這種比較方式,就定義一個StringComparator比較器,並實現compare方法。調用Arrays.sort時傳入這個比較器就可以了。
③Comparator接口其實是對comparable的一種擴展,更方便用户自定義多種比較邏輯。

原來Arrays.sort中利用我們自定義的比較大小的方法來進行排序,也就是調用了我們實現Comparator接口中的compare方法來實現排序的。如果沒有傳入定義的Comparator,那麼對象必須實現comparable接口才能夠比較大小,不然無法將對象進行排序,在Java中 Integer,String,double 等都實現了comparable接口所以可以直接比較大小。