JDK源碼學習(一) Object

注意:基於JDK1.8

位置

java.lang.Object

介紹

public class Object

Object爲所有類的根類,所有的類都將Object類作爲父類,每個對象(包括數組)都實現了Object類的方法

方法詳細信息

private static native void registerNatives()

native方法,表明不是用Java代碼實現,而是由C/C++去完成,並被編譯成了.dll,由Java去調用,方法的具體實現根據平臺的不同應該也是不同的。使用native修飾,即表示操作系統,需要提供此方法,Java本身需要使用。具體到registerNatives()方法本身,其主要作用是將C/C++中的方法映射到Java中的native方法,實現方法命名的解耦。在源碼中還有一個靜態代碼塊,會在加載Object時調用,對幾個本地方法進行註冊(也就是初始化java方法映射到C的方法)

private static native void registerNatives();
static {
	registerNatives();
}
public final native Class< ? > getClass()

返回此Object的運行時類。 返回的類對象是被表示類的static synchronized方法鎖定的對象,這裏返回的是真正的class對象,而不是父類的class對象,如下

Object obj = new Object();
Class clazz = obj.getClass();
System.out.println(clazz);
// result: class java.lang.Object
Number num = 1;
Class<? extends Number> number_clazz = num.getClass();
System.out.println(number_clazz);
// result: class java.lang.Integer

Number對象num實際類型爲java.lang.Integer

public native int hashCode()

OpenJDK8 默認hashCode的計算方法是通過和當前線程有關的一個隨機數+三個確定值,運用隨機數算法得到的一個隨機數。和對象內存地址無關。

支持這種方法一般是爲了散列表,如HashMap。hashCode一般需要符合三種特性
1.只要在執行Java應用程序時多次在同一個對象上調用該方法, hashCode方法必須始終返回相同的整數, 該整數不需要在不同的應用程序中保持一致
2.如果根據equals(Object)方法兩個對象相等,則在兩個對象中的每個對象上調用hashCode方法必須產生相同的整數結果
3.如果兩個對象根據equals(java.lang.Object)方法不相等,那麼在兩個對象中的每個對象上調用hashCode方法可以產生不同的整數結果。 但是,爲不等對象生成不同的整數結果可能會提高哈希表的性能

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

默認equals方法使用的是 ==, 也就是比較兩個對象地址是否相同,一般都需要重寫equals方法,如String類中,就是比較String中的實際存儲數據的char數組是否相同

 public boolean equals(Object anObject) {
		// 判斷是相同對象直接返回真
        if (this == anObject) {
            return true;
        }
        // 判斷是否是String類或者其子類,如果不是返回假
        if (anObject instanceof String) {
            String anotherString = (String)anObject;
            int n = value.length;
            // 比較長度是否相等,如果不等返回假
            if (n == anotherString.value.length) {
                char v1[] = value;
                char v2[] = anotherString.value;
                int i = 0;
                // 判斷char數組對應位置的值是否相同
                while (n-- != 0) {
                    if (v1[i] != v2[i])
                        return false;
                    i++;
                }
                return true;
            }
        }
        return false;
    }

equals方法一般也需要滿足5個特性
1.自反性 :對於任何非空的參考值x , x.equals(x)應該返回true 。
2.對稱性 :對於任何非空引用值x和y , x.equals(y)應該返回true當且僅當y.equals(x)回報true 。
3.傳遞性 :對於任何非空引用值x , y和z ,如果x.equals(y)回報true個y.equals(z)回報true ,然後x.equals(z)應該返回true 。
4.一致的 :對於任何非空引用值x和y ,多次調用x.equals(y)始終返回true或始終返回false ,沒有設置中使用的信息equals比較上的對象被修改。
5.對於任何非空的參考值x , x.equals(null)應該返回false

protected native Object clone() throws CloneNotSupportedException

創建一個新的內存空間,複製一個對象,這裏是淺拷貝,下面舉個栗子:

// 創建一個Student對象,這裏必須實現Cloneable方法纔可以調用clon()方法,否則會報異常
public class Student implements Cloneable{
    private String name;
    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

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

// clone Student 對象可以發現克隆對象的hashCode變了(默認的toString()方法爲返回值爲class對象名加hashCode),可以判斷不是同一個對象
Student stu = new Student();
Student stu1 = (Student) stu.clone();
System.out.println(stu);
//result: com.liao.Student@4554617c
System.out.println(stu1);
//result: com.liao.Student@74a14482

// 可以看到成員變量name爲引用類型,改變stu的name值sut1也改變,這個說明爲clone方法爲淺拷貝
 Student stu = new Student();
 stu.setName("同學");
 stu.setAge(10);
 Student stu1 = (Student) stu.clone();
 stu.setName("你好");
 stu.setAge(2);
 System.out.println(stu.getName() + ":" + stu.getAge());
 //result: 你好:2

// 數組也有clone()方法,所有數組都被認爲是實現接口Cloneable ,並且數組類型T[]的clone方法的返回類型是T[] 
int[] arr = new int[] {1, 2, 4};
int[] arr1 = (int[])arr.clone();
System.out.println(Arrays.toString(arr));
System.out.println(Arrays.toString(arr1));

結論:

  1. object的clone()方法會創建一個新的對象
  2. object的clone()方法爲淺拷貝
  3. 調用clone()方法的類需要實現Cloneable標識接口,否則會則拋出CloneNotSupportedException異常
  4. 數組也有clone()方法,所有數組都被認爲是實現接口Cloneable ,並且數組類型T[]的clone方法的返回類型是T[]
public String toString()

返回對象的字符串表示形式,默認返回class對象的名字@hashCode的16進制

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

導致當前線程等待,直到另一個線程調用該對象的 notify()方法或 notifyAll()方法

public final void wait(long timeout, int nanos) throws InterruptedException

導致當前線程等待,直到另一個線程調用 notify()方法或該對象的 notifyAll()方法,或者指定的時間已過,這裏實際調用的是上面的wait(long timeout)方法,我們可以看到傳遞的納秒值實際上只做了判斷並沒有實際使用,也就是實際上只有毫秒起了作用

 public final void wait(long timeout, int nanos) throws InterruptedException {
        if (timeout < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }
		// 只判斷了nanos即納秒值,並沒有使用
        if (nanos < 0 || nanos > 999999) {
            throw new IllegalArgumentException(
                                "nanosecond timeout value out of range");
        }

        if (nanos > 0) {
            timeout++;
        }
		// 調用 wait(timeout)方法
        wait(timeout);
    }

public final void wait() throws InterruptedException

導致當前線程等待,直到另一個線程調用該對象的 notify()方法或 notifyAll()方法,這個方法調用的實際是本地方法 wait(long timeout)

// 生產者
public final void wait() throws InterruptedException {
        wait(0);
    }

下面對notify/notifyAll和wait的用法舉一個栗子,就用比較經典的生產者消費者問題

public class Process implements Runnable {
    //傳遞數據
    private List<String> queue;
    private int count = 0;
    //對象,作爲鎖
    private Object lock;

    public Process(List queue, Object lock) {
        this.queue = queue;
        this.lock = lock;
    }

    @Override
    public void run() {
        while (true) {
            synchronized (lock) {
                // 當queue中沒有數據時,生產數據,否則等待釋放鎖
                if (queue != null && queue.size() == 0) {
                    System.out.println("生產了" + count);
                    queue.add("麪包" + count++ + "號");
                    // 休眠一段時間
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                } else {
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                // 喚醒其他線程
                lock.notify();
            }
        }
    }
}

//消費者
public class Consumer implements Runnable {
    // 傳遞數據
    private List<String> queue;
    //作爲對象鎖
    private Object lock;

    public Consumer(List queue, Object lock) {
        this.queue = queue;
        this.lock = lock;
    }


    @Override
    public void run() {
        while (true) {
            synchronized (lock) {
                // 當queue中有數據時,消費數據,否則等待,釋放鎖
                if (queue != null && queue.size() > 0) {
                    String pro = this.queue.remove(0);
                    System.out.println("消費了" + pro);
                } else {
                    try {
                        this.lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                // 喚醒其他線程
                lock.notify();
            }
        }
    }
}

測試

//測試
 List<String> queue = new ArrayList<>();
 Object lock = new Object();
 Process process = new Process(queue, lock);
 Consumer consumer = new Consumer(queue, lock);

Thread processThread = new Thread(process);
Thread consumerThread = new Thread(consumer);
processThread.start();
consumerThread.start();

運行結果

生產了0
消費了麪包0號
生產了1
消費了麪包1號
生產了2
消費了麪包2號
生產了3
消費了麪包3號
生產了4
消費了麪包4號
...

有以下幾點想進行說明

  1. 同步時需要使用同一把對象鎖
  2. wait時會釋放鎖,然後調用notify方法可以喚醒一個線程
protected void finalize() throws Throwable

當垃圾收集確定不再有對該對象的引用時,垃圾收集器在對象上調用該方法。 一個子類覆蓋了處理系統資源或執行其他清理的finalize方法,下面舉個栗子

class Test {
    @Override
    protected void finalize() throws Throwable {
//        super.finalize();
        System.out.println("我被調用了");
    }
}


測試

Test test = new Test();
test = null;
// 一般是不使用gc方法的,因爲這個方法並不一定會導致JVM發生垃圾回收,存在不確定性,這裏是爲了測試
System.gc();
Thread.sleep(5000);

運行結果

我被調用了

需要注意的是finalize()方法的調用,可能會導致對象生命週期被延長,導致內存溢出

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