Java Object類的各個方法

Java中所有的類都繼承自java.lang.Object類,Object類中一共有11個方法:

public final native Class<?> getClass();

public native int hashCode();

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

protected native Object clone() throws CloneNotSupportedException;

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

public final native void notify();

public final native void notifyAll();

public final native void wait(long timeout) throws InterruptedException;

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 > 0) {
        timeout++;
    }

    wait(timeout);
}

public final void wait() throws InterruptedException {
    wait(0);
}

protected void finalize() throws Throwable { }

getClass方法

這是一個native方法,并且是'final'的,也就是說這個方法不允許在子類中覆寫。
getClass方法返回的是當(dāng)前實例對應(yīng)的Class類,也就是說不管一個類有多少個實例,每個實例的getClass返回的Class對象是一樣的。請看下面的例子:

Integer i1 = new Integer(1);
Class i1Class = i1.getClass();

Integer i2 = new Integer(1);
Class i2Class = i2.getClass();
System.out.println(i1Class == i2Class);

上面的代碼運行結(jié)果為true,也就是說兩個Integer的實例的getClass方法返回的Class對象是同一個。

Integer.class和int.class

Java中還有一個方法可以獲取Class,例如我們想獲取一個Integer實例對應(yīng)的Class,可以直接通過Integer.class來獲取,請看下面的例子:

Integer num = new Integer(1);
Class numClass = num.getClass();
Class integerClass = Integer.class;
System.out.println(numClass == integerClass);

上面代碼的運行結(jié)果為true,也就是說通過調(diào)用實例的getClass方法和類.class返回的Class是一樣的。與Integer對象的還有int類型的原生類,與Integer.class對應(yīng),int.class用于獲取一個原生類型int的Class。但是他們兩個返回的不是同一個對象。請看下面的例子:

Class intClass = int.class;
Class intTYPE = Integer.TYPE;
Class integerClass = Integer.class;
System.out.println(intClass == integerClass);
System.out.println(intClass == intTYPE);

上面的代碼運行結(jié)果是:

false
true

Integer.classint.class返回的不是一個對象,而int.class返回的和Integer.TYPE是同一個對象。Integer.TYPE定義如下:

public static final Class<Integer>  TYPE = (Class<Integer>) Class.getPrimitiveClass("int");

Java中為原生類型(boolean,byte,char,short,int,long,float,double)和void都創(chuàng)建了一個預(yù)先定義好的類型,可以通過包裝類的TYPE靜態(tài)屬性獲取。上述的Integer類中TYPE和int.class是等價的。

hashCode方法

對象的哈希碼主要用于在哈希表中的存放和查找等。Java中對于對象hashCode方法的規(guī)約如下:

  1. 在java程序執(zhí)行過程中,在一個對象沒有被改變的前提下,無論這個對象被調(diào)用多少次,hashCode方法都會返回相同的整數(shù)值。對象的哈希碼沒有必要在不同的程序中保持相同的值。
  2. 如果2個對象使用equals方法進行比較并且相同的話,那么這2個對象的hashCode方法的值也必須相等。
  3. 如果根據(jù)equals方法,得到兩個對象不相等,那么這2個對象的hashCode值不需要必須不相同。但是,不相等的對象的hashCode值不同的話可以提高哈希表的性能。

為了理解這三條規(guī)約,我們來先看一下HashMap中put一個entry的過程:

public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
    Node<K,V>[] tab; Node<K,V> p; int n, i;
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
    if ((p = tab[i = (n - 1) & hash]) == null) // 這里key的hashcode被用來定位當(dāng)前entry在哈希表中的index
        tab[i] = newNode(hash, key, value, null); // 如果當(dāng)前哈希表中沒有key對應(yīng)的entry,則直接插入
    else {
        // 當(dāng)前哈希表中已經(jīng)有了key對應(yīng)的entry了,則找到這個節(jié)點,然后看是否需要更新這個entry的value
        Node<K,V> e; K k;
        // 判斷當(dāng)前節(jié)點就是已經(jīng)存在的entry的條件:1:hashcode相等;2:當(dāng)前節(jié)點的key和key是同一個對象(==)或者兩者的equals方法判定相等
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            e = p;
        else if (p instanceof TreeNode)
            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
        else {
            for (int binCount = 0; ; ++binCount) {
                if ((e = p.next) == null) {
                    p.next = newNode(hash, key, value, null);
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        treeifyBin(tab, hash);
                    break;
                }
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    break;
                p = e;
            }
        }
        // 是否需要更新舊值,如果需要更新則更新
        if (e != null) { // existing mapping for key
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null)
                e.value = value;
            afterNodeAccess(e);
            return oldValue;
        }
    }
    ++modCount;
    if (++size > threshold)
        resize();
    afterNodeInsertion(evict);
    return null;
}

HashMap中put一個鍵值對時有以下幾個步驟:

  1. 計算key的哈希碼,通過哈希碼定位新增的entry應(yīng)該處于哈希表中的哪個位置,如果當(dāng)前位置為null,則代表哈希表中沒有key對應(yīng)的entry,直接新插入一個節(jié)點就好了。
  2. 如果當(dāng)前key在哈希表中已經(jīng)有了映射,則先查找這個節(jié)點,判定當(dāng)前是否為目標(biāo)節(jié)點的條件有兩個:1)兩者的哈希碼必須相等;2)兩者是同一個對象(==成立),或者兩者的equals方法判定兩者相等。
  3. 判斷是否需要更新舊值,需要的話就更新。

分析完了HashMap的put操作后,我們再來看看這三條規(guī)約:

  1. 第一條規(guī)約要求多次調(diào)用一個對象的hashCode方法返回的值要相等。設(shè)想如果一個key的hashCode方法每次返回值如果不同,則在put的時候就可能定位到哈希表中不同的位置,就產(chǎn)生了歧義:明明兩個key是同一個,但是哈希表中存在同一個key的多個不同映射。這就違背了哈希表的key不能重復(fù)的原則了。
  2. 第二條規(guī)約也很好理解:如果兩個key的equals方法判定兩者相等,則說明哈希表中只需要保留一個key就行了。如果equals判定相等,而hashcode不同,則就會違背這個事實。
  3. 第三條規(guī)約是用來優(yōu)化哈希表的性能的,如果哈希表put時"碰撞"太多,勢必會造成查找性能下降。

equals方法

equals方法用于判定兩個對象是否相等。Object中的equals方法其實默認(rèn)比較的是兩個對象是否擁有相同的地址。也就是"=="對應(yīng)的內(nèi)存語義。

但是在子類中我們可以覆寫equals來判定子類的兩個實例是否為同一個對象。例如對于一個人來說,他有很多屬性,但是每個人的id是唯一的,如果兩個人的id一樣則證明兩個人是同一個人,而不管其他屬性是否一樣。這個時候我們就可以覆寫人的equals方法來保證這點了。

public class Person {
    
    private long id;
    private String name;
    private int age;
    private String nation;

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return id == person.id; // 如果兩個person的id相同,則我們認(rèn)為他們是同一個對象
    }

    @Override
    public int hashCode() {
        return Objects.hash(id);
    }
}

equals方法在非空對象引用上的特性:

  1. reflexive,自反性。任何非空引用值x,對于x.equals(x)必須返回true
  2. symmetric,對稱性。任何非空引用值x和y,如果x.equals(y)為true,那么y.equals(x)也必須為true
  3. transitive,傳遞性。任何非空引用值x、y和z,如果x.equals(y)為true并且y.equals(z)為true,那么x.equals(z)也必定為true
  4. consistent,一致性。任何非空引用值x和y,多次調(diào)用 x.equals(y) 始終返回 true 或始終返回 false,前提是對象上 equals 比較中所用的信息沒有被修改
  5. 對于任何非空引用值 x,x.equals(null) 都應(yīng)返回 false

Java要求一個類的equals方法和hashCode方法同時覆寫。我們剛剛分析了HashMap中對于key的處理過程:首先根據(jù)key的哈希碼定位哈希表中的位置,其次根據(jù)"=="或者equals方法判定兩個key是否相同。如果Person的equals方法沒有被覆寫,則兩個Person對象即使id一樣,但是不是指向同一塊內(nèi)存地址,那么哈希表中就查找不到已經(jīng)存在的映射entry了。

clone方法

用于克隆一個對象,被克隆的對象需要implements Cloneable接口,否則調(diào)用這個對象的clone方法,將會拋出CloneNotSupportedException異常??寺〉膶ο笸ǔG闆r下滿足以下三條規(guī)則:

  1. x.clone() != x,克隆出來的對象和原來的對象不是同一個,指向不同的內(nèi)存地址
  2. x.clone().getClass() == x.getClass()
  3. x.clone().equals(x)

一個對象進行clone時,原生類型和包裝類型的field的克隆原理不同。對于原生類型是直接復(fù)制一個,而對于包裝類型,則只是復(fù)制一個引用而已,并不會對引用類型本身進行克隆。

淺拷貝

淺拷貝例子:

public class ShallowCopy {

    public static void main(String[] args){
        Man man = new Man();
        Man manShallowCopy = (Man) man.clone();
        System.out.println(man == manShallowCopy);
        System.out.println(man.name == manShallowCopy.name);
        System.out.println(man.mate == manShallowCopy.mate);
    }
}

class People implements Cloneable {

    // primitive type
    public int id;
    // reference type
    public String name;

    @Override
    public Object clone() {
        try {
            return super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return null;
    }
}

class Man extends People implements Cloneable {
    // reference type
    public People mate = new People();

    @Override
    public Object clone() {
        return super.clone();
    }
}

上面的代碼的運行結(jié)果是:

false
true
true

通過淺拷貝出來的Man對象manShallowCopy的name和mate屬性和原來的對象man都指向了相同的內(nèi)存地址。在對Man的name和mate進行拷貝時淺拷貝只是對引用進行了拷貝,指向的還是同一塊內(nèi)存地址。

深拷貝

對一個對象深拷貝時,對于對象的包裝類型的屬性,會對其再進行拷貝,從而達(dá)到深拷貝的目的,請看下面的例子:

public class DeepCopy {

    public static void main(String[] args){
        Man man = new Man();
        Man manDeepCopy = (Man) man.clone();
        System.out.println(man == manDeepCopy);
        System.out.println(man.mate == manDeepCopy.mate);
    }
}

class People implements Cloneable {

    // primitive type
    public int id;
    // reference type
    public String name;

    @Override
    public Object clone() {
        try {
            return super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return null;
    }
}

class Man extends People implements Cloneable {
    // reference type
    public People mate = new People();

    // 深拷貝Man
    @Override
    public Object clone() {
        Man man = (Man) super.clone();
        man.mate = (People) this.mate.clone(); // 再對mate屬性進行clone,從而達(dá)到深拷貝
        return man;
    }
}

上面代碼的運行結(jié)果為:

false
false

Man對象的clone方法中,我們先對Man進行了clone,然后對mate屬性也進行了拷貝。因此man的mate和manDeepCopy的mate指向了不同的內(nèi)存地址。也就是深拷貝。

通常來說對一個對象進行完完全全的深拷貝是不現(xiàn)實的,例如上面的例子中,雖然我們對Man的mate屬性進行了拷貝,但是無法對name(String類型)進行拷貝,拷貝的還是引用而已。

toString方法

Object中默認(rèn)的toString方法如下:

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

也就是class的名稱+對象的哈希碼。一般在子類中我們可以對這個方法進行覆寫。

notify方法

notify方法是一個final類型的native方法,子類不允許覆蓋這個方法。

notify方法用于喚醒正在等待當(dāng)前對象監(jiān)視器的線程,喚醒的線程是隨機的。一般notify方法和wait方法配合使用來達(dá)到多線程同步的目的。
在一個線程被喚醒之后,線程必須先重新獲取對象的監(jiān)視器鎖(線程調(diào)用對象的wait方法之后會讓出對象的監(jiān)視器鎖),才可以繼續(xù)執(zhí)行。

一個線程在調(diào)用一個對象的notify方法之前必須獲取到該對象的監(jiān)視器(synchronized),否則將拋出IllegalMonitorStateException異常。同樣一個線程在調(diào)用一個對象的wait方法之前也必須獲取到該對象的監(jiān)視器。

wait和notify使用的例子:

Object lock = new Object();
Thread t1 = new Thread(() -> {
    synchronized (lock) {
        System.out.println(Thread.currentThread().getName() + " is going to wait on lock's monitor");
        try {
            lock.wait();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " relinquishes the lock's monitor");
    }
});
Thread t2 = new Thread(() -> {
    synchronized (lock) {
        System.out.println(Thread.currentThread().getName() + " is going to notify a thread that waits on lock's monitor");
        lock.notify();
    }
});
t1.start();
t2.start();

notifyAll方法

notifyAll方法用于喚醒所有等待對象監(jiān)視器鎖的線程,notify只喚醒所有等待線程中的一個。

同樣,如果當(dāng)前線程不是對象監(jiān)視器的所有者,那么調(diào)用notifyAll同樣會發(fā)生IllegalMonitorStateException異常。

wait方法

public final native void wait(long timeout) throws InterruptedException;

wait方法一般和上面說的notify方法搭配使用。一個線程調(diào)用一個對象的wait方法后,線程將進入WAITING狀態(tài)或者TIMED_WAITING狀態(tài)。直到其他線程喚醒這個線程。

線程在調(diào)用對象的wait方法之前必須獲取到這個對象的monitor鎖,否則將拋出IllegalMonitorStateException異常。線程的等待是支持中斷的,如果線程在等待過程中,被其他線程中斷,則拋出InterruptedException異常。

如果wait方法的參數(shù)timeout為0,代表等待過程是不會超時的,直到其他線程notify或者被中斷。如果timeout大于0,則代表等待支持超時,超時之后線程自動被喚醒。

finalize方法

protected void finalize() throws Throwable { }

垃圾回收器在回收一個無用的對象的時候,會調(diào)用對象的finalize方法,我們可以覆寫對象的finalize方法來做一些清除工作。下面是一個finalize的例子:

public class FinalizeExample {

    public static void main(String[] args) {
        WeakReference<FinalizeExample> weakReference = new WeakReference<>(new FinalizeExample());
        weakReference.get();
        System.gc();
        System.out.println(weakReference.get() == null);
    }

    @Override
    public void finalize() {
        System.out.println("I'm finalized!");
    }
}

輸出結(jié)果:

I'm finalized!
true

finalize方法中對象其實還可以"自救",避免垃圾回收器將其回收。在finalize方法中通過建立this的一個強引用來避免GC:

public class FinalizeExample {

    private static FinalizeExample saveHook;

    public static void main(String[] args) {
        FinalizeExample.saveHook = new FinalizeExample();
        saveHook = null;
        System.gc();
        System.out.println(saveHook == null);
    }

    @Override
    public void finalize() {
        System.out.println("I'm finalized!");
        saveHook = this;
    }
}

輸出結(jié)果:

I'm finalized!
false
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容