1. 程式人生 > 其它 >原始碼系列(三)----Object類

原始碼系列(三)----Object類

技術標籤:JDK原始碼

java是一門面向物件的程式語言,所有的事物都可以抽象為物件。

Object類是所有類的超類,位於java.lang包下,可以看做是所有類的父類,因此所有類都具備它的實現方法,或自己重寫,或直接使用。

1.註冊本地函式

在Object中存在這樣頭一段程式碼

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

在靜態程式碼塊中,呼叫了registerNatives()函式,也就是說在Object類載入過程中,便執行了registerNatives()函式。

這個函式的作用是向JVM(Java Virtual Machine,我們的java不是直接執行在作業系統之上,而是執行在java虛擬機器上)註冊Object的本地函式hashCode()、clone()、notify()、notifyAll()、wait()方法(不包含getClass())。

2.getClass()

public final native Class<?> getClass();

getClass()用於獲取某物件執行時類物件,這是獲取執行時類物件的方法之一,另一種方式是Class.forName(包名 + 類名)的方式。

同一類的例項都是有該類的類物件生成。建立新的物件可以通過new 的方式,通過Class物件,我們可以使用它的newInstance()

函式呼叫其無參建構函式,完成新物件的建立;如果需要使用有參建構函式,需要獲取Constructor物件(以後有機會在介紹)。

執行以下程式碼:

class MyTest{
    static int count;
    public MyTest(){
        System.out.println("建立的第" + (++count) + "個物件例項...");
    }
}
public class Main {

    public static void main(String[] args) throws Exception {

        // MyTest的物件例項
        MyTest myTest = new MyTest();

        // 獲取執行時類物件
        Class clazz1 = myTest.getClass();
        Class clazz2 = Class.forName("com.guaniu.MyTest");

        // 建立新的物件例項
        MyTest instance1 = (MyTest) clazz1.newInstance();
        MyTest instance2 = (MyTest) clazz2.newInstance();
        System.out.println("clazz1 == clazz2:" + (clazz1 == clazz2));
        System.out.println("instance1 == instance2:" + (instance1 == instance2));
    }

}

得出結果:

3.hashCode()

public native int hashCode();

hashCode()方法返回的是32位的int型別值,在底層實現中是將物件地址的轉換。這個方法主要是用於支援雜湊表的資料結構,如HashCode。

hashCode需要滿足的原則是:

a.在同一個java程式中對用同一個物件的多次呼叫,返回的數值必須是一致的,在不同的Java程式中不需要滿足此規則;

b.如果通過equals方法判斷兩個物件相等,那麼兩個物件hashCode()必須返回相等的數值;

c.如果兩個物件不相等,不需要要求它們的hashCode()方法返回相同的數值。

4.equals()

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

equals()方法用於判斷兩個物件是否相等,沒有要求相同(==),雖然在Object的原有實現中是“==”的方式。

equals() 方法需要滿足一下性質:

a.自反性(reflexive):對於任何非null引用x,x.equals(x)為true;

b.對稱性(symmetric):對於任何非空引用x和y,如果x.equals(y)為true,y.equals(x)必須也為true;

c.傳遞性(transitive):對於任何非空引用x、y和z,如果x.equals(y)為true,y.equals(z)為true,那麼x.equals(z)也為true;

d.一致性(consistent):對於任何非空引用x和y,對於多次呼叫x.equals(y)始終返回的是true和false,不會改變。

e.對於任意非空引用x,x.equals(null)返回false。

5.clone()

protected native Object clone() throws CloneNotSupportedException;

可以看到這個方法是使用protected關鍵字修飾的,這方法用於返回當前物件的複製。然而,通過物件呼叫這個方法需要實現Cloneable介面,然後重寫這個方法(如果需要在其他包中呼叫,還需要將其宣告為public方法),否則會丟擲CloneNotSupportedException。

Cloneable介面是一個空介面,並沒有包含clone()方法的宣告!!!但是僅實現Cloneable介面,而不重寫clone()方法是不能使用這個方法的。

public interface Cloneable {}

案例分析1

class MyTest implements Cloneable{
    int i1;
    Integer i2;
    String str;
    Content content = new Content();

    public MyTest(int i1, int i2, String str){
        this.i1 = i1;
        this.i2 = i2;
        this.str = str;
    }

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

    @Override
    public String toString() {
        return "MyTest{" +
                "i1=" + i1 +
                ", i2=" + i2 +
                ", str='" + str + '\'' +
                ", content=" + content +
                '}';
    }
}

class Content{
    String value = "test";

    @Override
    public String toString() {
        return "Content{" +
                "value='" + value + '\'' +
                '}';
    }
}
public class Main {

    public static void main(String[] args) throws Exception {

        MyTest myTest = new MyTest(128, 128, "aaa"); // 因為Integer數值在[-128,127]之間時會從快取中獲取,因此我的測試用例選擇大於127的值
        MyTest cloneMyTest = myTest.clone();

        System.out.println("myTest:" + myTest.toString());
        System.out.println("cloneMyTest:" + cloneMyTest.toString());

        System.out.println("myTest.i2 == cloneMyTest.i2:" + (myTest.i2 == cloneMyTest.i2));
        System.out.println("myTest.str == cloneMyTest.str:" + (myTest.str == cloneMyTest.str));
        System.out.println("myTest.content == cloneMyTest.content:" + (myTest.content == cloneMyTest.content));

        cloneMyTest.i1 = 129;
        cloneMyTest.i2 = 130;
        cloneMyTest.str = "bbb";
        cloneMyTest.content.value = "new content";

        System.out.println("myTest:" + myTest.toString());
        System.out.println("cloneMyTest:" + cloneMyTest.toString());
    }

}

執行結果:

結論:a.克隆物件的基本型別的成員變數的修改不會影響到原來的物件;

b.克隆物件的包裝型別的成員變數的修改不會影響到原來的物件;

c.克隆物件的String型別的成員變數的修改不會影響到原來的物件;

d.克隆物件的自定義型別【引用型別】的成員變數的修改會影響原來的物件;

解決辦法時Content類同樣需要實現Cloneable介面,在MyTest類的clone()方法中,在克隆物件返回之前,呼叫content的clone()方法,如下所示:

class MyTest implements Cloneable{
    int i1;
    Integer i2;
    String str;
    Content content = new Content();

    public MyTest(int i1, int i2, String str){
        this.i1 = i1;
        this.i2 = i2;
        this.str = str;
    }

    @Override
    protected MyTest clone() throws CloneNotSupportedException {
        MyTest myTest = (MyTest) super.clone();
        myTest.content = this.content.clone();
        return myTest;
    }

    @Override
    public String toString() {
        return "MyTest{" +
                "i1=" + i1 +
                ", i2=" + i2 +
                ", str='" + str + '\'' +
                ", content=" + content +
                '}';
    }
}

class Content implements Cloneable{
    String value = "test";

    @Override
    public String toString() {
        return "Content{" +
                "value='" + value + '\'' +
                '}';
    }

    @Override
    protected Content clone() throws CloneNotSupportedException {
        return (Content) super.clone();
    }
}
public class Main {

    public static void main(String[] args) throws Exception {

        MyTest myTest = new MyTest(128, 128, "aaa"); // 因為Integer數值在[-128,127]之間時會從快取中獲取,因此我的測試用例選擇大於127的值
        MyTest cloneMyTest = myTest.clone();

        System.out.println("myTest:" + myTest.toString());
        System.out.println("cloneMyTest:" + cloneMyTest.toString());

        System.out.println("myTest.i2 == cloneMyTest.i2:" + (myTest.i2 == cloneMyTest.i2));
        System.out.println("myTest.str == cloneMyTest.str:" + (myTest.str == cloneMyTest.str));
        System.out.println("myTest.content == cloneMyTest.content:" + (myTest.content == cloneMyTest.content));

        cloneMyTest.i1 = 129;
        cloneMyTest.i2 = 130;
        cloneMyTest.str = "bbb";
        cloneMyTest.content.value = "new content";

        System.out.println("myTest:" + myTest.toString());
        System.out.println("cloneMyTest:" + cloneMyTest.toString());
    }

}

程式碼執行結果:

為什麼會產生這樣的結果呢?

首先先介紹兩個概念:

淺拷貝:對於拷貝物件內部的引用型別變數,只拷貝了原有物件引用成員變數的引用,兩個引用實際指向的還是同一個物件。

深拷貝:對於拷貝物件內部的引用型別變數,對原有物件引用所指向的物件進行了拷貝,兩個引用指向的不是同一個變數。

java中對於基本型別成員變數來說,拷貝的就是值;對於引用型別來說,預設的拷貝型別就是淺拷貝(不管是包裝類、String、還是其他引用型別)。

在上面的例子中,對Integer和String型別拷貝是引用拷貝,也就是淺拷貝,對於賦新值得過程中,生成了新的物件,使他們指向了新生成的物件。

6.toString()

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

toString()方法就是物件的字串展示方法,預設是【型別 + @ + hash碼十六進位制表示】

7.notify()/notifyAll()

public final native void notify();
public final native void notifyAll();

這兩個方法用於喚醒在當前物件鎖等待的執行緒,notify()隨機喚醒一條等待執行緒,notifyAll()喚醒所有等待執行緒,這些執行緒競爭物件的監視器鎖許可權,實際同一時刻只能有一條執行緒能夠獲得鎖許可權。

使用notify/notifyAll的執行緒當前應該通過synchronized持有這物件所,而被喚醒的執行緒則等待該執行緒退出同步方法或者同步程式碼塊,才能獲取到鎖。

8.wait()

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 >= 500000 || (nanos != 0 && timeout == 0)) {
       timeout++;
    }

    wait(timeout);
}

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

可以看到wait/notify方法都是使用final關鍵字修飾的,屬於不能被重寫的方法。

wait()用於使當前執行緒在其呼叫位置等待,它會釋放鎖的佔用,中斷該執行緒或者其他執行緒呼叫notify/notifyAll可使該執行緒脫離該狀態。

wait有三種方法可供使用:

a.無參,一致等待直到其他執行緒使用notify/notifyAll;

b.一個引數timeout(毫秒),等待timeout的時間直到其他執行緒使用notify/notifyAll或者超時;

c.兩個引數timeout(毫秒),nanos(納秒),可以看到,對於納秒的計算實際上是類似四捨五入的形式,等待round(timeout + nanos)的時間直到其他執行緒使用notify/notifyAll或者超時;

9.finalize()

protected void finalize() throws Throwable { }

當前物件回收前會被自動呼叫,個人不要使用!!!。

總結:

1、Object是Java中所有類的超類;

2、深拷貝與淺拷貝的認識和使用;

3、物件鎖等待、與喚醒方法的認識。

wait()和notify()涉及到了多執行緒的內容,具體內容後面再細講。