Object類 所有類的基類

Java 是一門典型的面向?qū)ο笳Z(yǔ)言,提供 extends 關(guān)鍵字使子類繼承父類咒劲。

public class Student extends Person {
    ...
}

但是創(chuàng)建 Person 類時(shí)不用使用 extends 繼承 Object類继薛。

public class Person extends Object {
    ...
}

因?yàn)椋瑒?chuàng)建的類沒(méi)有明確指明繼承關(guān)系時(shí)爽待,會(huì)在編譯時(shí)自動(dòng)繼承 Object 類损同。可以使用 Object 類型的變量引用任何類型的實(shí)例:

Object sample = new Student(1001, "小咖", 20, "男");

也可以將任意的 Object 實(shí)例轉(zhuǎn)換為需要的類型:

Person p = (Person) sample;

上面兩個(gè)例子在編譯器中是不會(huì)報(bào)錯(cuò)的鸟款。因此膏燃,Object 類是 Person 類的父類,每個(gè)類都是由 Object 擴(kuò)展而來(lái)的何什。所以组哩,熟悉 Object 類中提供的服務(wù)是十分重要的专普。

Java 中的基本類型不是對(duì)象锚沸,但也提供了相應(yīng)的包裝類型,如 int 基本類型對(duì)應(yīng) Integer 包裝類型。但使用基本類型創(chuàng)建的數(shù)組是擴(kuò)展自 Object 類散庶,實(shí)際上是引用。

Object sample = new int[10];

其實(shí)仪际,所有的數(shù)組類型都是擴(kuò)展了 Object 類咸作。

下表為 Object 類的通用方法。

方法 描述 異常
final native Class<?> getClass() 返回對(duì)象的運(yùn)行時(shí)類 無(wú)
native int hashCode() 返回對(duì)象的散列碼 無(wú)
boolean equals(Object obj) 與其它對(duì)象是否相等 無(wú)
native Object clone() 克隆并返回對(duì)象的副本 CloneNotSupportedException
String toString() 返回對(duì)象的字符串表示 無(wú)
final native void notify() 喚醒正在等待對(duì)象監(jiān)聽(tīng)器上的一個(gè)線程 無(wú)
final native void notifyAll() 喚醒正在等待對(duì)象監(jiān)聽(tīng)器上的所有線程 無(wú)
final native void wait() 導(dǎo)致當(dāng)前線程等待们豌,直到另一個(gè)線程調(diào)用此對(duì)象的notify()notifyAll() InterruptedException
final native void wait(long timeout) 導(dǎo)致當(dāng)前線程等待涯捻,直到另一個(gè)線程調(diào)用此對(duì)象的notify()notifyAll(),或者指定時(shí)間已到 InterruptedException
final void wait(long timeout, int nanos) 導(dǎo)致當(dāng)前線程等待望迎,直到另一個(gè)線程調(diào)用此對(duì)象的notify()notifyAll()障癌,或者指定時(shí)間已到 InterruptedException
void finalize() 當(dāng)GC確定不再有對(duì)該對(duì)象的引用時(shí),由對(duì)象的 GC 調(diào)用此方法 Throwable

equals()

Object 類提供了 equals() 方法用于檢測(cè)對(duì)象是否相等辩尊。其實(shí)現(xiàn)相等性要滿足五個(gè)條件:

  1. 自反性涛浙。 對(duì)于任何的非空引用都需滿足 x.equals(x) == true
  2. 對(duì)稱性摄欲。 對(duì)于任何引用都滿足 x.equals(y) == y.equals(x)轿亮。
  3. 傳遞性。 對(duì)于任何引用都滿足 (x.equals(y) && y.equals(z)) == x.equals(z)胸墙。
  4. 一致性我注。 對(duì)于不發(fā)生任何變化的引用都滿足 x.equals(y) == x.equals(y),多次調(diào)用 equals() 方法結(jié)果不變迟隅。
  5. null 的比較但骨。x.equals(null) 返回 false。對(duì)任何不是 null 的對(duì)象調(diào)用 equals() 方法與 null 比較的結(jié)果都為 false智袭。

引用類型最好使用 equals() 方法比較奔缠;而基本類型使用 == 比較。

其中补履,子類中定義 equals() 方法時(shí)添坊,需先比較超類的 equals()。如果檢測(cè)失敗箫锤,對(duì)象就不可能相等贬蛙。如果超類中的域都相等,就需要比較子類中的實(shí)例域谚攒。

下面可以從兩個(gè)截然不同的情況看一下這個(gè)問(wèn)題:

  • 如果子類能夠擁有自己的相等概念阳准,則對(duì)稱性需求將強(qiáng)制采用 getClass 進(jìn)行檢測(cè)。
  • 如果由超類決定相等的概念馏臭,那么就可以使用 instanceof 進(jìn)行檢測(cè)野蝇,這樣可以在不同子類的對(duì)象之間進(jìn)行相同的比較讼稚。

對(duì)象中使用 equals() 方法比較需要實(shí)現(xiàn)的步驟如下:

  1. 檢查是否為同一個(gè)引用,如果是直接返回 true绕沈。
  2. 檢測(cè)傳入的值是否為 null锐想,如果是直接返回 false
  3. 檢測(cè)是否屬于同一個(gè)類型乍狐。如果不是直接返回 false赠摇。
  4. Object 對(duì)象類型轉(zhuǎn)為要比較的類型。
  5. 比較每個(gè)關(guān)鍵域是否相等浅蚪。
public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    Person person = (Person) o; 
    return Objects.equals(name, person.name) 
            && Objects.equals(sex, person.sex)
            && (age != person.age);
}

如果子類重新定義 equals藕帜,就要在其中包含 super.equals(other)

hasCode()

hashCode() 返回的是整數(shù)值惜傲,是無(wú)規(guī)律的散列碼洽故。hashCode() 定義在了 Object 類中,每個(gè)類都可以使用 hashCode()方法調(diào)用自身散列碼盗誊,其值為對(duì)象的存儲(chǔ)地址时甚。

Object sample = new Student(1001, "小咖", 20, "男");
System.out.println(sample); // [I@5b464ce8

如果你在創(chuàng)建的類中覆蓋了 equals() 方法,就必須覆蓋 hashCode() 方法 哈踱。這是 hashCode() 的通用約定撞秋。下面是覆蓋 hashCode() 方法的約定:

  • 程序執(zhí)行期間,對(duì)象的 equals() 方法中比較的信息不變嚣鄙,同一個(gè)對(duì)象的 hashCode() 方法的返回值也不變。兩個(gè)程序的執(zhí)行期間串结,hashCode() 方法返回的值可以不一致哑子。
  • 如果兩個(gè)對(duì)象根據(jù) equals(Object) 方法比較相等,那 hashCode() 方法的返回值也必須相等肌割。
  • 如果兩個(gè)對(duì)象根據(jù) equals(Object) 方法比較不相等卧蜓,那 hashCode() 方法的返回值最好不相等。如果相等把敞,會(huì)在使用 Map 時(shí)造成散列碼沖突弥奸。

總結(jié)就是兩個(gè)對(duì)象相等,其散列碼一定相同奋早;但是散列碼相同的兩個(gè)對(duì)象并不一定相等盛霎。因?yàn)橛?jì)算散列碼具有隨機(jī)性,兩個(gè)值不同的對(duì)象可能計(jì)算出相同的散列碼耽装。

理想的散列函數(shù)是把集合中不相等的實(shí)例均勻地分布到所有可能的 int 之上愤炸。但非常困難,只能實(shí)現(xiàn)相對(duì)接近這種理想的情形掉奄。

當(dāng)計(jì)算散列碼時(shí)规个,要將每個(gè)域都考慮進(jìn)去。可以將每個(gè)域都當(dāng)成 R 進(jìn)制的某一位诞仓,然后組成一個(gè) R 進(jìn)制的整數(shù)缤苫。

R 一般取奇數(shù) 31,偶數(shù)會(huì)出現(xiàn)乘法溢出墅拭,信息會(huì)丟失活玲。因?yàn)榕c 2 相乘相當(dāng)于向左移一位,最左邊的位丟失帜矾。并且一個(gè)數(shù)與 31 相乘可以轉(zhuǎn)換成移位和減法:31*x == (x<<5)-x翼虫,編譯器會(huì)自動(dòng)進(jìn)行這個(gè)優(yōu)化。

如下是一個(gè)如何簡(jiǎn)單的計(jì)算散列碼的參考:

  • 基本類型調(diào)用 Type.hashCode(value) 來(lái)生成屡萤。Type 類型為基本類型各自的包裝類珍剑。
  • 如果是引用類型,并且需要覆蓋 equals() 方法死陆,equals() 方法使用哪些域比較招拙,hashCode() 方法也會(huì)遞歸地調(diào)用這些域的散列碼并計(jì)算。
  • 如果是數(shù)組類型措译,那數(shù)組中的每個(gè)值都當(dāng)做單獨(dú)的域來(lái)處理别凤。也可以使用 Arrays.hashCode() 方法計(jì)算。

不要試圖從散列碼計(jì)算中排除掉一個(gè)對(duì)象的關(guān)鍵域來(lái)提高性能领虹。

下面重寫 Student 類的 hashCode() 方法:

@Override
public int hashCode() {
    int result = super.hashCode();
    result = 31 * result + sid;
    return result;
}

hashCode() 方法返回的散列碼也可以是負(fù)數(shù)规哪,合理地組合實(shí)例域的散列碼,以便能夠讓各個(gè)不同的對(duì)象產(chǎn)生的散列碼更加均勻塌衰。

toString()

默認(rèn)的 toString() 方法是返回的是 java.lang.Object@511baa65 這種類型是的字符串诉稍。

Student sample = new Student(1001, "小咖", 20, "男");
System.out.println(sample); // java.lang.Object@511baa65

上面 System.out.println(sample) 會(huì)自動(dòng)調(diào)用 sample.toString() 方法將值輸出到控制臺(tái)。但是最疆,提供好的 toString() 實(shí)現(xiàn)可以獲取對(duì)象狀態(tài)的必要信息杯巨,也易于調(diào)試。因此建議為每個(gè)自定義的類覆蓋 toString() 方法努酸。

下面重寫 Student 類的 toString() 方法:

@Override
public String toString() {
    return getClass().getName() 
            + "{ sid=" + sid
            + ", name=" + super.getName()
            + ", age=" + super.getAge()
            + ", sex=" + super.getSex()
            + " }";
}

如果父類 Person 也重寫類 toString() 方法:

@Override
public String toString() {
    return getClass().getName() + 
            "{" + '\'' +
            "name='" + name + '\'' +
            ", age=" + age +
            ", sex='" + sex + '\'' +
            '}';
    }

并且子類的 toString() 也可以調(diào)用:

@Override
public String toString() {
    return super.toString() + "{sid = " + sid + "}";
}
// xxx.Student{'name='小咖', age=20, sex='男'}{sid = 1001}

clone()

Object 類提供了 clone() 方法用于克隆實(shí)例服爷,但因?yàn)槭?protected 修飾符所修飾的方法,因此不會(huì)顯示地覆蓋 clone() 获诈。實(shí)現(xiàn) Cloneable 接口的類可以覆蓋 clone() 方法提供克隆仍源。如果不實(shí)現(xiàn),會(huì)拋出 CloneNotSupportedException 異常舔涎。正確的寫法如下所示:

public class Person implements Cloneable {
    private String name;
    private int age;
    private String sex;
    private String[] address;
    public Person(String name, int age, String sex) {
        this.name = name;
        this.age = age;
        this.sex = sex;
    }
    
    ...省略getter與setter...

    @Override
    protected Person clone() throws CloneNotSupportedException {
        return (Person) super.clone();
    }
}

Java 支持協(xié)變返回類型镜会,也就是覆蓋方法的返回類型可以是被覆蓋方法的返回類型的子類。并且在 clone() 方法中調(diào)用 super.clone() 方法得到功能完整的克隆對(duì)象终抽。

當(dāng)使用 Person 創(chuàng)建對(duì)象并調(diào)用 clone() 方法克隆戳表。

Person s1 = new Person("小卡", 22, "男");
s1.setAddress(new String[] {"浙江省", "江蘇省", "湖南省"});
Person s2 = sample.clone();
System.out.println(s1.hashCode()); // 873415566
System.out.println(s2.hashCode()); // 818403870
System.out.println(s1 == s2); // false
System.out.println(s1.getAddress() == s2.getAddress()); // true

從上面的代碼中得出桶至,調(diào)用 clone() 方法獲得的對(duì)象是個(gè)新的對(duì)象,但是對(duì)象中的引用還是原來(lái)的引用匾旭,而不是新引用镣屹。這次的克隆被稱為 淺拷貝

9-1 淺拷貝.png

使用 clone() 方法與通過(guò)構(gòu)造器創(chuàng)建對(duì)象實(shí)際上是一樣的价涝,要確保不會(huì)傷害到原始的對(duì)象女蜈,并確保正確地創(chuàng)建被克隆的對(duì)象中的約束條件。這次的克隆被稱為 深拷貝色瘩。

9-2 深拷貝.png

因此伪窖,在 Person 類的內(nèi)部,address 數(shù)組也要遞歸地調(diào)用 clone() 方法:

@Override
protected Person clone() throws CloneNotSupportedException {
    Person result = (Person) super.clone();
    result.address = address.clone();
    return result;
}

記住居兆,Cloneable 與引用可變對(duì)象的 final 域的正常用法是不兼容的覆山。因此,實(shí)現(xiàn) clone() 方法禁止給 final 賦新值泥栖。

上述的拷貝方式比較復(fù)雜簇宽。可以在類中提供一個(gè)拷貝構(gòu)造器或拷貝工廠來(lái)實(shí)現(xiàn)克隆的替代功能吧享。

public Person(Person value) {...}
public static Person newInstance(Person value) {...}

finalize()

當(dāng) GC 確定不再有對(duì)該對(duì)象的引用時(shí)魏割,GC 會(huì)調(diào)用對(duì)象的 finalize() 方法來(lái)清除回收。

protected void finalize() throws Throwable { }

因此钢颂,子類可以通過(guò)覆蓋此方法處理一些額外的清理工作钞它。 但是,finalize() 方法何時(shí)被調(diào)用取決于 Java VM殊鞭,而且不保證 finalize() 方法會(huì)被及時(shí)地執(zhí)行 须揣。因此,不要依賴 finalize() 方法來(lái)更新重要的持久狀態(tài)钱豁。

Java VM 會(huì)確保一個(gè)對(duì)象的 finalize() 方法只被調(diào)用一次,而且程序中不能直接調(diào)用 finalize() 方法疯汁。

finalize() 方法通常也不可預(yù)測(cè)牲尺,而且很危險(xiǎn),一般情況下幌蚊,不必要覆蓋 finalize() 方法谤碳。

wait 與 notify

Object 對(duì)象提供了 wait()notify() 方法,這兩個(gè)方法的使用是相對(duì)的:

  • wait():線程進(jìn)入等待狀態(tài)溢豆。
  • notify():?jiǎn)拘训却搶?duì)象的線程蜒简。

使用 wait() 方法必須在同步區(qū)域內(nèi)部調(diào)用,這個(gè)同步區(qū)域?qū)?duì)象鎖定在調(diào)用 wait() 方法的對(duì)象上漩仙。下面是使用 wait() 方法的標(biāo)準(zhǔn)模式:

synchronized (obj) {
    while (<condition does not hold>) {
        obj.wait();
    }
}

這時(shí)的 obj 對(duì)象所在線程會(huì)處于等待狀態(tài)搓茬,需要使用 notify() 方法喚醒 obj 所在線程犹赖。

synchronized (obj) {
    obj.notify();
}

最好使用 notifyAll() 方法喚醒線程。因?yàn)榭倳?huì)產(chǎn)生正確的結(jié)果卷仑,保證會(huì)喚醒所有需要被喚醒的線程峻村。雖然也會(huì)喚醒其他線程,但不影響程序的正確性锡凝,而且 notifyAll() 方法代替 notify() 方法可以避免來(lái)自不相關(guān)線程的意外或惡意的等待粘昨。

<small>注意:必須使用 synchronized,否則會(huì)報(bào) IllegalMonitorStateException 異常窜锯。</small>

總結(jié)

這里對(duì) Object 類做了一個(gè)簡(jiǎn)單的了解张肾,知道 Object類是一切類的基類,可以引用一切的引用類型锚扎,包括數(shù)組類型吞瞪。且 Object 中提供的方法需要在適合的場(chǎng)景下覆蓋得到最佳的結(jié)果,最好始終都覆蓋 toString() 方法工秩。

歡迎關(guān)注公眾號(hào)「海人為記」尸饺,期待與你共同進(jìn)步!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末助币,一起剝皮案震驚了整個(gè)濱河市浪听,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌眉菱,老刑警劉巖迹栓,帶你破解...
    沈念sama閱讀 211,290評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異俭缓,居然都是意外死亡克伊,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門华坦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)愿吹,“玉大人,你說(shuō)我怎么就攤上這事惜姐±绻颍” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 156,872評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵歹袁,是天一觀的道長(zhǎng)坷衍。 經(jīng)常有香客問(wèn)我,道長(zhǎng)条舔,這世上最難降的妖魔是什么枫耳? 我笑而不...
    開(kāi)封第一講書人閱讀 56,415評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮孟抗,結(jié)果婚禮上迁杨,老公的妹妹穿的比我還像新娘钻心。我一直安慰自己,他們只是感情好仑最,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,453評(píng)論 6 385
  • 文/花漫 我一把揭開(kāi)白布扔役。 她就那樣靜靜地躺著,像睡著了一般警医。 火紅的嫁衣襯著肌膚如雪亿胸。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 49,784評(píng)論 1 290
  • 那天预皇,我揣著相機(jī)與錄音侈玄,去河邊找鬼。 笑死吟温,一個(gè)胖子當(dāng)著我的面吹牛序仙,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播鲁豪,決...
    沈念sama閱讀 38,927評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼潘悼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了爬橡?” 一聲冷哼從身側(cè)響起治唤,我...
    開(kāi)封第一講書人閱讀 37,691評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎糙申,沒(méi)想到半個(gè)月后宾添,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,137評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡柜裸,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,472評(píng)論 2 326
  • 正文 我和宋清朗相戀三年缕陕,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片疙挺。...
    茶點(diǎn)故事閱讀 38,622評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡扛邑,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出铐然,到底是詐尸還是另有隱情蔬崩,我是刑警寧澤,帶...
    沈念sama閱讀 34,289評(píng)論 4 329
  • 正文 年R本政府宣布锦爵,位于F島的核電站,受9級(jí)特大地震影響奥裸,放射性物質(zhì)發(fā)生泄漏险掀。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,887評(píng)論 3 312
  • 文/蒙蒙 一湾宙、第九天 我趴在偏房一處隱蔽的房頂上張望樟氢。 院中可真熱鬧冈绊,春花似錦、人聲如沸埠啃。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,741評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)碴开。三九已至毅该,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間潦牛,已是汗流浹背眶掌。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留巴碗,地道東北人朴爬。 一個(gè)月前我還...
    沈念sama閱讀 46,316評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像橡淆,于是被迫代替她去往敵國(guó)和親召噩。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,490評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容