原始碼系列(三)----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()
執行以下程式碼:
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()涉及到了多執行緒的內容,具體內容後面再細講。