Java基礎-Object類中的方法

下面這些是 Java 中的 Object 類中方法,共 11 個,9 種方法,wait() 方法被重載了。

方法

描述

protected native Object clone()

創建並返回當前對象的一份拷貝

public boolean equals(Object obj)

比較兩個對象是否相等

protected void finalize()

當對象要被垃圾回收前,調用該方法

public final native Class<?> getClass()

返回該類的 Class 對象

public native int hashCode()

返回該對象的哈希碼值

public final native void notify()

喚醒正在等待的對象監視器所在的單個線程

public final native void notifyAll()

喚醒正在等待的對象監視器所在的所有線程

public String toString()

返回對象的字符串表示

public final void wait()

線程等待

public final native void wait(long timeout)

在規定的時間內線程等待

public final void wait(long timeout, int nanos)

在規定的時間內線程等待

我們知道 Java 的繼承是單繼承的,也即繼承樹是單根繼承,樹的根就是 Object 類,Java 中的所有類都直接或間接繼承自 Object,無論是否明確指明,無論類是否是抽象類。Object 類可以說是 Java 類的始祖類,其中有一些方法也是預留給了後代類,也即是上面表中沒有 final 關鍵字修飾的方法,有 clone() 方法,equals() 方法,finalize() 方法,hashCode() 方法,toString() 方法,這些都是常見的被子類重寫的方法,下面就針對這些方法做一個探討。

1、clone() 方法

從字面意上看,這個方法被設計爲克隆對象,返回一個和被克隆的對象一模一樣的對象。這個方法被 protect 關鍵字修飾,說明只能被子類重寫和調用。但是在子類重寫該方法時,必須要加上一個 “克隆標記” 的接口 Cloneable,這個接口裏面什麼方法都沒有,純粹就是一個 “標記”。這個方法被 native 關鍵字修飾,所以可以看出這個是一個本地方法,最終調用的是外部的鏈接庫(C語言或C++寫成),非 Java 代碼實現。

下面通過實驗看看 clone() 方法的真相。

public class CloneTest implements Cloneable{
    public String name;

    public CloneTest(String name) {
        this.name = name;
    }

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

    public static void main(String[] args) throws CloneNotSupportedException {
        CloneTest c1 = new CloneTest("小明");
        CloneTest c2 = (CloneTest) c1.clone();
        System.out.println(c1 == c2); // false
        System.out.println(c1.name == c2.name); // true
    }
}

代碼運行的結果已經寫在了代碼註釋中,該類沒有實際重寫父類中的 clone() 方法,只是簡單的調用了父類的 clone() 方法。可以看到 c1 所引用的對象中 name 字段和 c2 所引用的對象的 name 字段地址相同,說明 c1.name 和 c2.name 都是對 “小明” 這一個字符串對象的引用,而並沒有因克隆而產生一個新的 “小明” 字符串對象,也即是 clone() 方法本質上只是對 引用的複製(克隆),並沒有真正複製對應對象中的內容,所以這隻能算是一種 “淺克隆” 或者說是 “淺拷貝”。上面這段代碼如果改變 c1.name 的值,c2.name 的值不會跟着改變,但如果 像下面代碼這樣,改變了 name 對應的值,就會對克隆對象中對應字段的值造成影響。

public class CloneTest {
    static class Clothes {
        public String name;

        public Clothes(String name) {
            this.name = name;
        }
                
        @Override
        protected Object clone() throws CloneNotSupportedException {
            return super.clone();
        }
    }

    static class People implements Cloneable{
        public Clothes clothes;

        public People(Clothes clothes) {
            this.clothes = clothes;
        }

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

    public static void main(String[] args) throws CloneNotSupportedException {
        People p1 = new People(new Clothes("測試"));
        People p2 = (People) p1.clone();
        System.out.println(p1 == p2); // false
        System.out.println(p1.clothes == p2.clothes); // true
        System.out.println(p1.clothes.name == p2.clothes.name); // true
        p1.clothes.name = "小明";
        System.out.println(p2.clothes.name); // 小明
    }
}

所以要想真正實現 “ 深拷貝”,必須要遞歸克隆到字段不再是一個類類型的字段,對類類型也要調用對應的 clone () 方法,這樣就可以保證引用字段都被複制了一遍。

上面 People 類中的 clone() 方法可以寫成:

@Override
protected Object clone() throws CloneNotSupportedException {
    People people = (People) super.clone();
    people.clothes = (Clothes) clothes.clone();
    return people;
}

2、equals() 方法

equals方法用來判斷兩個對象是否相等,Obejct 類中的 equals() 方法和直接使用 == 運算符是一樣的,都是看引用是否相同,這點可以從源碼中看出。子類可以根據自己的需要重寫這個方法,並且重寫該方法時遵循以下規則:

  • 自反性,對象必須與自身相等,即 x.equals(x) 爲 true
  • 對稱性,如果 x.equals(y) 爲 true,那麼 y.equals(x) 也爲 true
  • 傳遞性,如果 x.equals(y) 爲 true,y.equals(z) 爲 true,那麼 x.equals(z) 也爲 true
  • 一致性,多次調用 equals() 方法,返回值應該始終是 true 或始終是 false
  • 對於非空的 null 值,x.equals(null) 爲 false

在重寫 equals() 方法時,也最好一併重寫 hashCode() 方法,使得當 equals() 方法返回 true 時,兩個對象的 hashCode 也是相同的。當然這並不是必須的,但爲什麼要這樣做呢?因爲如果對象要存儲在散列結構(如 HashTable、HashSet、HashMap)中時,判斷兩個對象是否相等依據是 hashCode() 方法,如果只重寫了 equals() 方法,而沒有重寫 HashCode() 方法,就可能會出現兩個對象 equals 是 true,但 hashCode 不同,造成我們認爲一樣的對象被重複放入這些散列結構中。使用 hashCode 比較對象的另一好處是可以提高性能和搜索效率,從原來的全部查找對比,變爲只查找一次,時間複雜度從原來的 O(n) 變爲了 O(1)。

注意: equals 爲 true 的對象,hashCode 也是相同的,但是反過來,hashCode 相同的兩個對象,equals 不一定爲 true。

equals() 就是一個普通的方法,返回 true 還是 false 的決定權在於我們。

常用的實現步驟:

  • 檢查是否是同一個對象的引用,如果是,直接返回 true
  • 檢查是否是同一類型,如果不是直接,返回 false,注意這裏判斷是否是同一類型,不能使用 instanceof 關鍵字,這個關鍵字判斷的是前面對象是否是後面類的實例或者子類的實例額,不夠準確。
  • 將 Object 對象轉型
  • 判斷每個關鍵域值是否相等(業務的實際需要)

實例代碼如下(來自 GitHub):

public class EqualExample {
    private int x;
    private int y;
    private int z;

    public EqualExample(int x, int y, int z) {
        this.x = x;
        this.y = y;
        this.z = z;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        EqualExample that = (EqualExample) o;

        if (x != that.x) return false;
        if (y != that.y) return false;
        return z == that.z;
    }
}

3、finalize() 方法

雖然這個方法也可以被子類重寫,但是極少被使用,這個方法和 C++ 的析構函數功能是不一樣的,C++ 中使用析構函數來清除一個對象,而 Java 中清除對象的工作是由 Java 虛擬機幫我們完成的。Java 中設計這個方法只是想在垃圾回收器將對象從內存中清除前做一些其他自定義的清理工作,在未來的 JDK 版本中,這個方法很有可能會被取消。在 Java 中,與 finalize() 方法相似的有 finally 語句塊,用來在異常發生後關閉一些資源(如文件),常和 try .. catch 語句結合使用。final 關鍵字和 finalize() 看上去也很相似,但是一點關係都沒有,final 關鍵詞可以用來修飾變量、屬性、方法和類,分別表示常量、屬性不可變、方法不可被重寫、類不可被繼承。

4、hashCode() 方法

hashCode() 方法當然就是用來返回一個對象的哈希碼(hashCode)啦,hashCode 是一個 int 類型的數字,如果我們沒有重寫一個類的 toString() 方法,而使用 System.out.println 打印這個類,調用的就是 Object 類中的 toString() 方法,輸出格式爲 類名@hashCode的十六進制數 ,其實 hashCode() 方法完全不用手寫,強大的 IDE 工具一般都有自動生成的功能,可以幫我們寫 hashCode() 方法和 equals() 方法,另外在 JDK7 中新增的 Objects 類,也有個靜態方法 hashCode(obj) 可以生成對象的 hashCode 值。

5、toString() 方法

這個方法就不多說了,相信都懂,通俗的說就是對象的自我介紹方法,嘿嘿。

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