Java 7之基礎 - 實現比較

轉載請註明出處:http://blog.csdn.net/mazhimazh/article/details/20038281



1、equals()方法


 equals()方法定義在Object類內並進行了簡單的實現,如下:

 public boolean equals(Object obj) {
        return (this == obj);
 }

比較兩個原始類型比較的是內容,而如果比較引用類型的話,可以看到是通過==符號來比較的,所以比較的是引用地址,如果要自定義比較規則的話,可以覆寫自己的equals()方法。 String 、Math、還有Integer、Double等封裝類重寫了Object中的equals()方法,讓它不再比較引用,而是比較對象中實際包含內容。在編寫equals()方法時需要遵守一些規定:

  • 對稱性:如果x.equals(y)返回是“true”,那麼y.equals(x)也應該返回是“true”。也就暗示了x與y是同一類型,因爲他們之間調用的方法相同,才能時刻保證一返回值的一致性。 
  • 反射性:x.equals(x)必須返回是“true”。 
  • 類推性:如果x.equals(y)返回是“true”,而且y.equals(z)返回是“true”,那麼z.equals(x)也應該返回是“true”。 
  • 還有一致性:如果x.equals(y)返回是“true”,只要x和y內容一直不變,不管你重複x.equals(y)多少次,返回都是“true”。 
  • 任何情況下,x.equals(null),永遠返回是“false”;x.equals(和x不同類型的對象)永遠返回是“false”。 
看一下String類的equals()實現,如下:

 public boolean equals(Object anObject) {
        if (this == anObject) {            // 反射性
            return true;
        }
        if (anObject instanceof String) { // 只有同類型的才能比較
            String anotherString = (String) anObject;
            int n = value.length;
            if (n == anotherString.value.length) {
                char v1[] = value;
                char v2[] = anotherString.value;
                int i = 0;
                while (n-- != 0) {
                    if (v1[i] != v2[i])
                            return false;
                    i++;
                }
                return true;  // 返回true時,表示長度相等,且字符序列中含有的字符相等
            }
        }
        return false;
    }

當我們自己要重寫equals()方法進行內容的比較時,可以遵守以下幾點:

(1)使用instanceof 操作符檢查“實參是否爲正確的類型”。
(2)對於類中的每一個“關鍵域”,檢查實參中的域與當前對象中對應的域值。

  1. 對於非float和double類型的原語類型域,使用==比較;
  2. 對於float域,使用Float.floatToIntBits(afloat)轉換爲int,再使用==比較;
  3. 對於double域,使用Double.doubleToLongBits(adouble) 轉換爲int,再使用==比較;
  4. 對於對象引用域,遞歸調用equals()方法;
  5. 對於數組域,調用Arrays.equals()方法。

無論是Arrays工具類中提供了各種常用數據類型的比較方法,還是對象引用域的equals()方法,其實最終都是轉換爲float、String等的類型,然後遵照前三個規則來進行比較的,而這些類型的比較方法已經得到了實現。來看一下Arrays類中雙精度浮點數的比較:

public static boolean equals(double[] a, double[] a2) {
        if (a==a2)
            return true;
        if (a==null || a2==null)
            return false;

        int length = a.length;
        if (a2.length != length)
            return false;

        for (int i=0; i<length; i++)
            if (Double.doubleToLongBits(a[i])!=Double.doubleToLongBits(a2[i]))  // 調用Double類中的方法進行比較
                return false;

        return true;
    }
有興趣的可以自己去看其他的實現源代碼。


一般來說,如果你要把一個類的對象放入集合中,那麼通常要爲其重寫equals()方法,讓他們比較地址值而不是內容值。特別地,如果要把你的類的對象放入散列中,那麼還要重寫hashCode()方法;要放到有序集合中,還要重寫compareTo()方法。 如上說到的在容器實現的部分中要進行詳細的講解,這裏不再做過多的解釋。

有興趣的可以去看集合的實現:傳送門:Java 7集合源碼  http://blog.csdn.net/column/details/java-jh.html




2、Comparable接口


一個類實現了 Camparable 接口表明這個類的對象之間是可以相互比較的。如果用數學語言描述的話就是這個類的對象組成的集合中存在一個全序。這樣,這個類對象組成的集合就可以使用 Sort 方法排序了。

這個接口定義在java.lang包下,其源代碼如下:

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

這個接口支持泛型,在實現compareTo()方法時可限定具體的比較類型。下面來舉一個例子,如下:

public class PersonBean implements Comparable<PersonBean> {
	public PersonBean(int age, String name) {
		this.age = age;     // 引用類型
	        this.name = name;   // 基本類型
	}
	int age = 0;
	String name = "";
	public int getAge() {
		return age;
	}

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

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}
        // 覆寫equals和hashCode方法
	public boolean equals(Object o) {
		if (!(o instanceof PersonBean)) {
			return false;
		}
		PersonBean p = (PersonBean) o;
		return (age == p.age) && (name.equals(p.name));
	}

	public int hashCode() {
		int result = 17;
		result = 31 * result + age;
		result = 31 * result + name.hashCode();
		return result;
	}
	public String toString() {
		return (age + "{" + name + "}");
	}

	public int compareTo(PersonBean person) {
		int cop = age - person.getAge();
		if (cop != 0)
			return cop;
		else
			return name.compareTo(person.name);
	}
}

如果兩個人的年齡相同,則通過字符串類中的compareTo()方法進行比較。來看String類的定義如下:

public final class String implements java.io.Serializable, Comparable<String>, CharSequence 
可以看到實現了Comparable接口並且還實現了接口中定義的方法,源代碼如下:

 public int compareTo(String anotherString) {
        int len1 = value.length;
        int len2 = anotherString.value.length;
        int lim = Math.min(len1, len2);   //  返回len1和len2中長度較小的一個
        char v1[] = value;
        char v2[] = anotherString.value;

        int k = 0;
        while (k < lim) {       // 根據字符序列中所含字符的Unicode編碼值來比較
            char c1 = v1[k];
            char c2 = v2[k];
            if (c1 != c2) {
                return c1 - c2;
            }
            k++;
        }
        return len1 - len2;     // 如果兩個字符串的k個字符相等,則長度較小的在前
    }

字符串最後都是轉換爲字符數組進行比較的。

將代碼放到List中進行排序:

public void compare() {
		List<PersonBean> list = new ArrayList<PersonBean>();
		list.add(new PersonBean(20, "Tom"));
		list.add(new PersonBean(20, "Jeff"));
		list.add(new PersonBean(30, "Mary"));
		list.add(new PersonBean(20, "Ada"));
		list.add(new PersonBean(61, "Peter"));
		list.add(new PersonBean(20, "Bush"));
		// 對List進行排序
		Collections.sort(list);
		for(int i=0;i<list.size();i++){
			System.out.println(list.get(i));
		}
	}

	public static void main(String[] args) {
		TestComparable tc = new TestComparable();
		tc.compare();
	}

或者也可以將代碼放到Array數組中進行排序:

運行如上的代碼:
public class TestComparable {
	public void compare() {
		PersonBean[] p = { new PersonBean(20, "Tom"),
				new PersonBean(20, "Jeff"), new PersonBean(30, "Mary"),
				new PersonBean(20, "Ada"), new PersonBean(40, "Walton"),
				new PersonBean(61, "Peter"), new PersonBean(20, "Bush") };
		
		System.out.println("before sort:\n" + Arrays.toString(p));
		Arrays.sort(p);
		System.out.println("after sort:\n" + Arrays.toString(p));
	}

	public static void main(String[] args) {
		TestComparable tc = new TestComparable();
		tc.compare();
	}

}
結果如下:
before sort:
[20{Tom}, 20{Jeff}, 30{Mary}, 20{Ada}, 40{Walton}, 61{Peter}, 20{Bush}]
after sort:
[20{Tom}, 20{Jeff}, 20{Bush}, 20{Ada}, 30{Mary}, 40{Walton}, 61{Peter}]



3、Comparator接口


這個接口可以實現如下的兩個功能:

  1. 如果類的設計師沒有考慮到 Compare 的問題而沒有實現 Comparable 接口,可以通過 Comparator 來實現比較算法進行排序;
  2. 進行自定義排序,例如升序、降序等。

這個接口定義在com.util包下,源代碼如下:

public interface Comparator<T> {
    int compare(T o1, T o2);
    boolean equals(Object obj);
}

在這個接口中定義了兩個方法。

class PersonBean{    // 沒有實現Comarable接口
	public PersonBean(int age, String name) {
		this.age = age;
		this.name = name;
	}

	int age = 0;
	String name = "";

	public int getAge() {
		return age;
	}

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

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}
	public String toString() {
		return (age + "{" + name + "}");
	}
}

可以不用實現equals()方法,因爲類默認繼承Object對象,Object對象中提供了對equals()方法的實現。

public class AlphDesc implements Comparator<PersonBean> {            // 自定義排序規則
	public int compare(PersonBean personA, PersonBean personB) {
		int cop = personA.age - personB.age;
		if (cop != 0)
			return cop;
		else
			return personB.getName().compareTo(personA.getName());
	}
}

運行如上程序:

PersonBean[] p = { new PersonBean(20, "Tom"),
				new PersonBean(20, "Jeff"), new PersonBean(30, "Mary"),
				new PersonBean(20, "Ada"), new PersonBean(40, "Walton"),
				new PersonBean(61, "Peter"), new PersonBean(20, "Bush") };
		
		System.out.println("before sort:\n" + Arrays.toString(p));
		AlphDesc desc = new AlphDesc();
		Arrays.sort(p, desc);
		System.out.println("after sort:\n" + Arrays.toString(p));

運行結果如下:

before sort:
[20{Tom}, 20{Jeff}, 30{Mary}, 20{Ada}, 40{Walton}, 61{Peter}, 20{Bush}]
after sort:
[20{Tom}, 20{Jeff}, 20{Bush}, 20{Ada}, 30{Mary}, 40{Walton}, 61{Peter}]

或者還可以選擇一些靈活的方法,如下:

public static Comparator<PersonBean> AgeComparator = new Comparator<PersonBean>() {
		@Override
		public int compare(PersonBean e1, PersonBean e2) {
			return e1.getAge() - e2.getAge();
		}
	};

	public static Comparator<PersonBean> NameComparator = new Comparator<PersonBean>() {
		@Override
		public int compare(PersonBean e1, PersonBean e2) {
			return e1.getName().compareTo(e2.getName());
		}
	};
將如上的代碼寫到PersonBean類中,然後在排序時指定使用哪一種排序,如下使用年齡進行排序:
List<PersonBean> list = new ArrayList<PersonBean>();
		list.add(new PersonBean(20, "Tom"));
		list.add(new PersonBean(20, "Jeff"));
		list.add(new PersonBean(30, "Mary"));
		list.add(new PersonBean(20, "Ada"));
		list.add(new PersonBean(61, "Peter"));
		list.add(new PersonBean(20, "Bush"));
		// 對List進行排序
		Collections.sort(list,PersonBean.AgeComparator);



4、equals()方法、Comarable和Comparator接口之間的比較


1 >> equals()方法與接口的比較

        equals()方法定義在Object類中並且提供了簡單的實現。而Java中所有的類都繼承了equals()方法,類似於Comarable接口,土生土長的,在編寫源代碼時就定義成這樣了。所以你無法改變Object類中定義的equals()方法、或者是不可變類String的compareTo()方法。

        爲了能夠按照自己的規則進行比較,Object類中的equals()方法沒有加final關鍵字,允許進行覆蓋重寫,類似於Comparator接口,允許自定義比較規則。但是與Comparable接口和Comparator接口比較起來,顯得功能不足,專業水準不夠。主要體現在如下幾個方面:

(1)接口支持範型,可以傳入具體的比較類型,而equals()方法以Object類型做爲參數,太泛化,比較時容易出問題。如:

public class test1 {
	public static void main(String args[]) {
		AA aa = new AA();
		BB bb = new BB();
		aa.equals(bb); // 調用aa中的equals()方法
		bb.equals(aa); // 調用bb中的equals()方法
	}
}

class AA {
}

class BB {
}

不同類型比較容易出問題,如傳入錯誤的類型在編譯時無法糾正,不同的實例調用的方法決定了不同的比較規則等等。

(2)比較結果準確性。equals()方法比較結果不準確,如果不相等,只是返回false,而使用接口中定義的方法來比較,可以比較出先後順序。

(3)equals()是方法,更加註重面向對象的思想(繼承覆蓋),而接口是類型,面向的是業務和功能。所以說接口範圍更大,在Comparable接口中甚至定義了equals()方法來滿足其比較的功能。


2 >> 接口之間的比較

Comparable 是通用的接口,用戶可以實現它來完成自己特定的比較,而 Comparator 可以看成一種算法的實現,在需要容器集合實現比較功能的時候,來指定這個比較器,這可以看成一種設計模式,將算法和數據分離。





如果想要了解Arrays.sort()方法的源代碼,請點擊:

http://www.cnblogs.com/gw811/archive/2012/10/04/2711746.html





發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章