C Objcet類

Object類是Java所有類的始祖,在Java中每個類都是由它擴展而來。如果一個類沒有指出它的超類,Object就被認爲是這個類的超類。JDK文檔中這樣描述Object類

ClassObject is the root of the class hierarchy. Every class hasObject as a superclass. All objects, including arrays, implement the methods of this class.
Object類中的方法大部分爲native方法,他們非常重要,因爲所有類都有這些方法。

1 equals方法

Object類中的equals方法用於檢測一個對象是否等於另一個對象。在Object類中,這個方法將判斷兩個對象是否具有相同的引用,如果具有相同的引用,他們一定是相等的。


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

它的源碼很簡單,僅僅用了一個”==“進行判斷。

1.1java對equals的規定

java語言規範要求equals方法具有以下的特性

  • 自反性:對於任何非空引用x,x.equals(x)應該返回true
  • 對稱性:對於任何引用x和y,當且僅當y.equals(x)返回true,x.equals(y)也應該返回true
  • 傳遞性:對於任何引用x,y,z,如果x.equals(y)返回true,y.equals(z)返回true,x.equals(z)也應該返回true
  • 一致性:如果x和y引用的對象沒有發生變化,那麼返回調用x.equals(y)應該返回同樣的結果。
  • 對於任意非空引用x,x.equals(null)返回false。
這一特性的JDK文檔描述是:

The equals method implements an equivalence relation on non-null object references:

  • It is reflexive: for any non-null reference valuex, x.equals(x) should returntrue.
  • It is symmetric: for any non-null reference valuesx and y,x.equals(y) should returntrue if and only ify.equals(x) returnstrue.
  • It is transitive: for any non-null reference valuesx, y, andz, ifx.equals(y) returnstrue and y.equals(z) returnstrue, thenx.equals(z) should return true.
  • It is consistent: for any non-null reference valuesx and y, multiple invocations ofx.equals(y) consistently returntrue or consistently returnfalse, provided no information used inequals comparisons on the objects is modified.
  • For any non-null reference valuex,x.equals(null) should return false.

1.2 重寫equals方法

如果類具有自己特有的邏輯相等概念,而且超類還沒有覆蓋以實現期望的行爲,那麼就需要重寫equals方法。這通常屬於“值類”的情形。正確地重寫equals方法是非常重要的,因爲沒有哪個類是孤立的,一個類的實例通常會被頻繁地傳遞給另一個類的實例,有許多類,包括集合類,都依賴於傳遞給他們的對象是否遵守了equals約定。
下面來看如何違反這個五個要求

1.2.1 自反性

這個要求僅僅要求對象必須等於其自身。很難想象會無意識地違反這一條。如果違反了這一條,然後把該類的實例添加到集合中,該集合會告訴你,你剛剛添加的實例不存在。。。這是多麼可怕的一件事

1.2.2 對稱性

先看一個例子,定義一個特殊的String類,重寫它的equals方法,用於比較忽略大小寫的字符串。
public class IgnoreString {
	private final String str;
	public IgnoreString(String str)
	{
		this.str=str;
	}
	@Override
	public boolean equals(Object obj) {
		if(obj instanceof String)
		{
			return str.equalsIgnoreCase(obj.toString());
		}
		return false;
	}
	public static void main(String[] args) {
		String s="abc";
		IgnoreString ignoreString=new IgnoreString("ABc");
		System.out.println(s.equals(ignoreString));
		System.out.println(ignoreString.equals(s));
	}

}

正如所料,ignoreString.equals(s)返回了true,然而它顯然不滿足自反性。

1.2.3編寫equals方法的建議

  1. 顯式參數命名爲otherObject,稍後需要將它轉換成另一個叫做other的變量
  2. 檢測this與otherObject是否引用同一個變量
    <span style="font-size:18px;">if(this==otherObject) return true;</span>
    這條語句只是一個優化。實際上,這是一種經常採用的形式。因爲計算這個等式要比一個一個地比較類中的域所付出的代價小得多。
  3. 檢測otherObject是否爲null,如果爲null,返回false。這項檢測是很必要的
    <span style="font-size:18px;">if(otherObject==null) return false</span>
  4. 比較this與otherObject是否屬於同一個類。如果equals的寓意在每個子類中有所改變,就是用getClass檢測:
    <span style="font-size:18px;">if(getClass!=otherObject.getClass())return false;</span>
    如果所有的子類都有統一的語義,就是用instanceof檢測
    <span style="font-size:18px;">if(!(otherObject instanceof ClassName)) return false</span>
  5. 將otherObject轉換爲對應的類類型變量
    <span style="font-size:18px;">ClassName other=(ClassName) otherObject</span>
  6. 現在開始對所有需要比較的域進行比較。是用==比較基本類型域,是用equals比較對象域。如果所有的域都匹配,就返回true;否則返回false;如果在子類中重新定義equals,就要在其中包含調用super.equals(other)

2 hashCode方法

2.1 散列碼的特點

散列碼(hash code)是由對象到處的一個整型值。散列碼沒有規律,如果x和y是兩個不同的對象,x.hashCode()和y.hashCode()基本不會相同。Object類每個對象都有一個默認的散列碼,其值爲對象的儲存地址。

  • 在Java應用的一次執行過程當中,對於同一個對象的hashCode方法的多次調用,他們應該返回同樣的值(前提是該對象的信息沒有發生變化)。
  • 對於兩個對象來說,如果使用equals方法比較返回true,那麼這兩個對象的hashCode值一定是相同的
  • 對於兩個對象來說,如果使用equals方法比較返回false,那麼這兩個對象hashCode值不要求一定不同(可以相同,可以不同),但是如果不同則可以提高應用的性能。
  • 對於Object類來說,不同的Object對象的hashCode值是不同的,因爲Object類的hashCode值表示的是對象的地址)。

2.2 重寫hashCode方法

下面是String類的hashCode()方法

public int hashCode() {
        int h = hash;
        if (h == 0 && value.length > 0) {
            char val[] = value;

            for (int i = 0; i < value.length; i++) {
                h = 31 * h + val[i];
            }
            hash = h;
        }
        return h;
    }

如果重新定義equals方法,就必須重新定義hashcode方法,以便用戶可以將對象插入到hashmap等集合中。另外,最好使用null安全的方法。如果其參數爲null,這個方法返回0,否則返回對參數調用hashCode的結果。

3 toString方法

public String toString() {
        return getClass().getName() + "@" + Integer.toHexString(hashCode());
    }

toString方法,它用於返回表示對象值的字符串。從源碼可以看出,這個返回的字符串是對象類的名字@它的16進制地址(散列碼)。

隨處可見toString方法的原因是:只要對象與一個字符串通過操作符”+“進行連接,Java編譯就會自動調用toString方法,以便獲得對象的字符串描述。

Java工程師建議程序員爲自定義的類增加toString方法。這樣可以很好的利用的日誌。

4 clone方法

首先要弄懂爲什麼要clone一個對象,爲什麼普通的賦值(=)不行?

我們經常遇到這種情況,需要得到一個對象,但是又不想對這個對象產生任何影響,既對克隆對象任何的屬性修改都不會影響到原對象。在普通的=賦值的情況下,兩個對象引用同一個位置,顯然不能滿足這種需求,這就需要進行“深拷貝”。

在Object類有這樣一個方法,可以產生原對象的一個拷貝。不過這個方法是protected的,也就是說任何一個自定義的類都不能調用clone方法。這裏來看一下Object類實現的clone方法,它是一個native的方法。對於這個clone方法,我們希望的是拷貝的時候,對它的各個域都進行拷貝。這對於基本數據類型或者數值來說,這個clone方法顯然是沒有問題的。但是如果對象中包含了對子對象的引用,拷貝的結果就會出問題。因爲默認的克隆操作是淺拷貝

因此更多的情況,我們需要重新定義clone方法。對於每個類都需要作出如下判斷:

  • 默認的clone方法是否滿足要求。
  • 默認的clone方法是否能夠通過調用子對象的clone得到修補。
  • 是否不應該使用clone。
檢測完成以後,如果確實需要實現clone方法,那必須做到以下兩點
  • 實現Cloneable接口
  • 使用public訪問修飾符重新定義clone方法

這裏的Cloneble是一個mark interface,它沒有定義任何方法,僅僅是爲了表明類設計者要進行克隆方法的重寫。如果沒有實現這個接口,又重寫了clone方法,就會產生一個checked exception。

所有的數組類型都包含一個clone方法,這個方法被設爲public,可以利用這個方法創建一個包含所有數據元素的一個新數組。













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