1. 程式人生 > 其它 >淺談java中Object類的方法

淺談java中Object類的方法

目錄

Java中Object類的方法

Object類中的方法總共有10個,其中有一個方法是私有的native修飾的,除了這個方法不討論外,本文主要討論的方法有9個(getClass、hashCode、equals、toString、wait、notify、notifyAll、finalize、clone)。

registerNatives() : void

    private static native void registerNatives();
    static {
        registerNatives();
    }

registerNatives()方法使用了native特徵修飾符修飾,我們看不到該方法的具體實現,並且也沒有註釋,所以本文暫時不研究這個方法。

getClass() : Class

原始碼註釋:
 * Returns the runtime class of this {@code Object}. The returned
 * {@code Class} object is the object that is locked by {@code
 * static synchronized} methods of the represented class.
原始碼:
public final native Class<?> getClass();

getClass()方法返回當前執行時的類Class,即當前呼叫getClass()方法的物件對應的Class(位元組碼)。這個方法一般在發射場景中使用。

hashCode() : int

原始碼註釋:
* Returns a hash code value for the object. This method is
* supported for the benefit of hash tables such as those provided by
* {@link java.util.HashMap}.   
原始碼:
public native int hashCode();

hashCode()方法返回值是當前呼叫hashCode()方法的物件其記憶體地址所對應的的雜湊值(hash value),並且這個方法在那些底層通過雜湊表(hash table)實現的集合有很重要的意義,這個方法一般搭配equals()方法使用。

Ps:Hash表的底層實現就是陣列+連結串列的資料結構,每一個元素在雜湊函式的作用下都對應一個雜湊值,根據這個值來決定元素在陣列中的索引下標位置,而連結串列用於將那些雜湊值相同的元素串成一條鏈,即連結串列。

假設需要儲存的元素:23、38、12、17、29、60。雜湊函式為:value % 11。

那麼hash表的結構大致如下:

雜湊函式能使一個數據序列的訪問過程更加迅速有效,通過雜湊函式,資料元素將被更快的定位,所以一般對訪問速度有要求的場景下,使用hash表作為儲存結構是優先選擇。

toString() : String

原始碼註釋:
* Returns a string representation of the object. In general, the
* {@code toString} method returns a string that
* "textually represents" this object. The result should
* be a concise but informative representation that is easy for a
* person to read.
* It is recommended that all subclasses override this method.
原始碼:
public String toString() {
        return getClass().getName() + "@" + Integer.toHexString(hashCode());
}

toString()方法用於返回物件的字串表示形式,Object類中的toString()方法返回的字串形式為類全名@記憶體地址,建議所有的子類都重寫toString()方法,因為toString()方法的初衷就是返回一個字串,並且我們能夠很容易的通過這個字串來區別同一個類下的物件,好比Person類下有三個屬性,分別是name、age、gender,當Person沒用重寫toString()方法時,預設呼叫的是Object類中toString()方法,那麼無論列印多少個Person類物件,它們的toString()方法返回值都是包名.Person@記憶體地址,完全達不到區分同類物件的需求,所以一般在類中重寫toString()方法,如下:

@Override
public String toString() {
	return "Person [name=" + name + ", age=" + age + ", gender=" + gender + "]";
}

equals() : boolean

原始碼註釋:
* Indicates whether some other object is "equal to" this one.
原始碼:
public boolean equals(Object obj) {
        return (this == obj);
}

equals()方法用於判斷兩個物件是否相等,Object類中的equals方法預設判斷兩個物件的地址引用是否相等。在實際開發中,一般需要根據業務邏輯重寫equals方法,根據物件的屬性值來判斷兩個物件在業務邏輯上是否相等,如Person類中有name屬性,如果不重寫equals方法,那麼預設呼叫的是Object類中equals方法,那麼所有Person物件都是不相等的,因為new出來的物件預設在堆記憶體中開闢一塊新的記憶體空間,那麼地址引用當然不相等。

Person類中的equals方法重寫如下:

@Override
public boolean equals(Object obj) {
    if (this == obj)
	return true;
    if (obj == null)
	return false;
    if (getClass() != obj.getClass())
	return false;
    Person other = (Person) obj;
    if (name == null) {
        if (other.name != null)
	    return false;
	} else if (!name.equals(other.name))
	    return false;
     return true;
}

重寫完equals方法之後,判斷兩個Person物件是否是同一個物件的判斷準則不在是物件的地址引用,而是Person物件name屬性值。這才是我們真正需要的結果。

== 和 equals的區別

==和equals都可用於物件是否相等。但是當equals方法被重寫之後,它們之間就略有不同。

==可以用於比較基本型別和引用型別,基本型別比較的是值是否相等,引用型別比較的是地址引用是否相等。

Object類中的equals方法只可以用於比較引用型別,預設比較的是地址引用是否相等。重寫後的equals方法不在是比較地址引用,而是比較物件的屬性值是否相等,String類就重寫了equals方法,所以通過String類的例項方法equals,比較的是兩個字串的每一個字元是否相等。

注意:equals方法一般需要結合hashCode方法重寫。

notify() : void

原始碼註釋:
Wakes up a single thread that is waiting on this object's
monitor. If any threads are waiting on this object, one of them
is chosen to be awakened. The choice is arbitrary and occurs at
the discretion of the implementation. A thread waits on an object's
monitor by calling one of the {@code wait} methods.
原始碼:
public final native void notify();

notify()方法使用了native特徵修飾符修飾,表示該方法使用了其他語言來實現,所以我們看不到notify()方法的具體實現,但是通過notify()方法的註釋,我們可以得知notify方法用於喚醒因執行緒同步進入阻塞狀態的任意一個執行緒。

notifyAll() : void

原始碼註釋:
Wakes up all threads that are waiting on this object's monitor. A
thread waits on an object's monitor by calling one of the
{@code wait} methods.
原始碼:
public final native void notifyAll();

notifyAll()方法和notify()方法基本差不多,不同的是notifyAll()方法不僅僅只喚醒一個因執行緒同步進入阻塞狀態的執行緒,而是喚醒所有。

注意:notify()方法和notifyAll()方法需要在同步程式碼塊或者同步方法中使用,因為這兩個方法就是用來喚醒因執行緒同步鎖進入阻塞狀態的執行緒,如果不線上程同步環境下,那麼notify()方法和notifyAll()方法需要喚醒的阻塞執行緒是哪一個呢?說白了就是不明確需要喚醒因哪一個執行緒同步鎖進入阻塞狀態的執行緒。而且如果不在同步程式碼塊或者同步方法內使用這兩個方法,編譯會報錯。

wait(long) : void

原始碼註釋:
Causes the current thread to wait until either another thread invokes the
{@link java.lang.Object#notify()} method or the
{@link java.lang.Object#notifyAll()} method for this object, or a
specified amount of time has elapsed.
The current thread must own this object's monitor.
原始碼:
public final native void wait(long timeout) throws InterruptedException;

wait(long)方法是native修飾的,即具體實現過程由其他語言(如C、C++)來實現。雖然我們不能通過原始碼來了解wait(long)方法的作用,但是我們可以通過註釋來了解wait(long)方法的功能或者作用,從註釋上可以知道,wait()方法的執行可以讓當前正在執行的執行緒進入阻塞狀態,直到其他執行緒呼叫notify()或者notifyAll()方法來喚醒這個執行緒,或者當過了timeout毫秒之後,自動從阻塞狀態進入就緒狀態。

注意:wait()方法是同步監視器呼叫的,而不是執行緒本身呼叫的,同時也要弄清楚執行緒和同步監視器之間的關係。

wait() : void

原始碼註釋:
Causes the current thread to wait until another thread invokes the
{@link java.lang.Object#notify()} method or the
{@link java.lang.Object#notifyAll()} method for this object.
In other words, this method behaves exactly as if it simply
performs the call {@code wait(0)}.
原始碼:
public final void wait() throws InterruptedException {
   wait(0);
}

wait()方法從原始碼上可以得知,其實wait()方法本質上就是wait(0),所以我們可以參考wait(long)方法來理解wait()方法的作用,因為wait()方法沒有引數,即沒有一個timeout值,所以當同步監視器呼叫了wait()方法後,進入阻塞的執行緒不存在過了timeout毫秒後自動進入就緒列隊(狀態)中,所以因為同步監視器呼叫wait()進入阻塞狀態的執行緒只有當其他執行緒呼叫notify()或者notifyAll()方法來喚醒其,使其進入就緒狀態。

wait(long, int) : void

原始碼註釋:
Causes the current thread to wait until another thread invokes the
{@link java.lang.Object#notify()} method or the
{@link java.lang.Object#notifyAll()} method for this object, or
some other thread interrupts the current thread, or a certain
amount of real time has elapsed.
@param      timeout   the maximum time to wait in milliseconds.
@param      nanos     additional time, in nanoseconds range 0-999999.
原始碼:
public final void wait(long timeout, int nanos) throws InterruptedException {
    if (timeout < 0) {
        throw new IllegalArgumentException("timeout value is negative");
    }

    if (nanos < 0 || nanos > 999999) {
        throw new IllegalArgumentException(
        "nanosecond timeout value out of range");
    }

    if (nanos >= 500000 || (nanos != 0 && timeout == 0)) {
        timeout++;
    }

    wait(timeout);
}

wait(long, int)方法通過其實現可以知道,該方法本質還是wait(long)方法,只不過和wait(long)方法不同之處是,wait(long, int)方法的對等待毫秒值的控制更加精確,其第一個引數是等待的毫秒值timeout,第二個引數是一個納秒值nanos,納秒值的最大值為999999,即約等於1毫秒,所有我們可以得到一個執行緒等待時間區間:[timeout, timeout+1],這時我們回到原始碼分析,當等待時間timeout小於0,那麼丟擲一個非法引數異常,當追加的納秒值nanos小於0或者大於999999時,也丟擲一個非法引數異常,當timeout和nanos的實參值都合法時,如果納秒值大於500000,那麼最後等待的總時間就是timeout+1,這裡可以理解為四捨五入,如果等待毫秒值timeout為0,並且追加的納秒值不等於0,那麼最後的等待毫秒值為timeout+1,即為1毫秒。

wait(long, int)方法其實是對執行緒的等待時間timeout進行了更加精細的控制。

finalize() : void

原始碼註釋:
Called by the garbage collector on an object when garbage collection
determines that there are no more references to the object.
A subclass overrides the {@code finalize} method to dispose of
system resources or to perform other cleanup.
原始碼:
protected void finalize() throws Throwable { }

當垃圾回收機制(Garbage Collection)確認了一個物件沒有被引用時,那麼垃圾回收機制就會回收這個物件,並在回收的前一刻這個物件呼叫finalize()方法,這類似C++中的解構函式,我們可以重寫物件的finalize()方法來研究垃圾回收機制回收物件的過程。

clone() : void

原始碼註釋:
Creates and returns a copy of this object.  
The precise meaning of "copy" may depend on the class of the object.
原始碼:
protected native Object clone() throws CloneNotSupportedException;

clone()方法用於克隆當前物件,克隆分為淺克隆和深克隆。Object類中的clone()方法是淺克隆物件。

淺克隆就是複製值一份,然後賦值給克隆出來的物件,所以當淺克隆基本資料型別時,那麼可以實現真正意義上的克隆,但是當淺克隆引用資料型別時,那麼就會出現複製地址引用一份,然後賦值給克隆物件,這種克隆僅僅只是克隆複製了一份地址引用,本質上這兩個物件操作的還是同一塊記憶體,所以達不到真正意義上的克隆物件,所以一般需要實現Cloneable介面,並重寫clone方法,當複製的屬性是引用資料型別時,進行遞迴克隆(基本資料型別直接複製一份,引用資料型別遞迴呼叫clone方法),重寫完clone方法才實現真正意義上的克隆。