1. 程式人生 > 程式設計 >「OpenJdk-11 原始碼-系列」 : Object

「OpenJdk-11 原始碼-系列」 : Object

Object

在 Java 中 Object 類是所有類的祖先類,Object 沒有定義屬性,一共有13個方法。其它所有的子類都會繼承這些方法。

建構函式

registerNatives()

 private static native void registerNatives();
 static {
        registerNatives();
 }
複製程式碼

在 Java 中,用 native 關鍵字修飾的函式表明該方法的實現並不是在Java中去完成,而是由C/C++去完成,並被編譯成了.dll,由Java去呼叫

registerNatives()方法的主要作用則是將C/C++中的方法對映到 Java 中的 native

方法,實現方法命名的解耦。

建構函式 public Object()

 @HotSpotIntrinsicCandidate
 public Object() {}
複製程式碼

@HotSpotIntrinsicCandidate註解,該註解是特定於Java虛擬機器器的註解。通過該註解表示的方法可能( 但不保證 )通過HotSpot VM自己來寫彙編或IR編譯器來實現該方法以提供效能。 它表示註釋的方法可能(但不能保證)由HotSpot虛擬機器器內在化。如果HotSpot VM用手寫彙編和/或手寫編譯器IR(編譯器本身)替換註釋的方法以提高效能,則方法是內在的。 也就是說雖然外面看到的在JDK9中weakCompareAndSet和compareAndSet底層依舊是呼叫了一樣的程式碼,但是不排除HotSpot VM會手動來實現weakCompareAndSet真正含義的功能的可能性

一般建立物件的時候直接使用 new className(Args) 來建立一個新的物件。而在類的定義過程中,對於未定義建構函式的類,那麼它就會預設繼承Object的無參建構函式,如果定了一個或多個建構函式,那麼就需要把無參建構函式方法也寫上。

方法

public final native Class<?> getClass()

 @HotSpotIntrinsicCandidate
 public final native Class<?> getClass();
複製程式碼

getClass返回執行時當前物件的類物件。在 Java 中,類是對具有一組相同特徵或行為的例項的抽象進行描述。而類物件

則是對的特徵和行為進行描述(即類的名稱,屬性,方法...)。也就是說通過獲取到類物件,則可以獲取到該類的所有屬性,方法等。

public native int hashCode()

@HotSpotIntrinsicCandidate
public native int hashCode();
複製程式碼

hashCode 返回當前物件的雜湊碼。hashCode遵守以下三個約定

  • 在 Java 程式執行期間,對同一個物件多次呼叫hashCode,那麼它們的返回值需要是一致的。(前提:沒有對物件進行修改)
  • 如果兩個物件相等(呼叫equals()方法),那麼這兩個物件的 hashCode也是一樣
  • 兩個物件呼叫hashCode方法返回的雜湊碼相等,這兩個物件不一定相等

也即是說,呼叫equals方法返回值相等,那麼呼叫hashCode方法返回值也一定相等。所以,在重寫euqlas方法之後,一定要重寫hashCode方法。

那麼判斷物件是否先等可以直接用equals來判斷,為什麼還需要hashCode方法呢?

其實hashCode方法的一個主要作用是為了增強雜湊表的效能。比如:我們知道Set集合不能存在相同的兩個物件,那麼該怎麼判斷兩個物件是否相同呢?如果沒有hashCode,那麼就需要進行遍歷來逐一判斷。那麼有hashCode,我們就可以計算出即將要加入集合的物件的hashCode,然後檢視集合中對應的位置上是否有物件即可。

public boolean equals(Object obj)

public boolean equals(Object obj) {
   return (this == obj);
}
    
複製程式碼

equals()用於判斷兩個物件是否相等。根據 Object 的實現,可以看到判斷的依據是看兩個物件的引用地址是否相等。

而一般我們會用另外一種方式來判斷是否相等。即==,==表示的是兩個變數值是否相等(基礎型別的值在記憶體地址中儲存的是值)

那麼我們想要判斷是否相等:

  • 如果是基礎型別,就可以直接用==來判斷
  • 如果是引用型別,那麼就需要通過equals方法來判斷(在實際業務中,一般會重寫equals方法)

需要注意的一點是String也是引用型別,我們判斷String的時候是直接使用的equals方法,而按照預設的equals實現,建立兩個具有相同值的String物件,那麼equals返回的應該是false

public boolean equals(Object anObject) {
    if (this == anObject) {
        return true;
    }
    if (anObject instanceof String) {
        String aString = (String)anObject;
        if (coder() == aString.coder()) {
            return isLatin1() ? StringLatin1.equals(value,aString.value)
                              : StringUTF16.equals(value,aString.value);
        }
    }
    return false;
}
複製程式碼

public String toString()

 public String toString() {  
    return getClass().getName() + "@" + Integer.toHexString(hashCode());  
}
複製程式碼

toString()返回該物件的字串表示。在使用 System.out.printLn(obj)的時候,其內部也是呼叫的toString方法。可以按需重寫toString方法。

protected native Object clone()

protected native Object clone() throws CloneNotSupportedException;
複製程式碼

clone()方法返回的是當前物件的引用,指向的是新clone出來的物件,此物件和原物件佔用不同的堆空間。

clone方法的正確呼叫需要實現 cloneable 介面,如果沒有實現該介面,那麼子類呼叫父類的 clone方法則會丟擲CloneNotSupportedException異常

Cloneable介面僅僅是一個表示介面,介面本身不包含任何方法,用來指示Object.clone()可以合法的被子類引用所呼叫。

1. 使用

先看一段程式碼

public class CloneTest {  
  
    public static void main(String[] args) {  
        Object o1 = new Object();  
        try {
            Object clone = o1.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
    }  
  
}  
複製程式碼

執行這段段程式碼會丟擲The method clone() from the type Object is not visible異常。原因是clone 方法是被 protected修飾的,也就是說被protected修飾的屬性和方法,在同一包下或在不同包下的子類可以訪問。顯然,CloneTestObject不在同一包下,不過按照字面意思,CloneTest會預設繼承Object,所以即使在不同的包下,應該也是可以訪問的才對。那麼問題就出現在「在不同包下的子類可以訪問」這句話上:

不同包中的子類可以訪問: 是指當兩個類不在同一個包中的時候,繼承自父類的子類內部且主調(呼叫者)為子類的引用時才能訪問父類用protected修飾的成員(屬性/方法)。 在子類內部,主調為父類的引用時並不能訪問此protected修飾的成員。(super關鍵字除外)

也就是說在子類中想要呼叫父類的protected方法,可以

  • 在子類中重寫父類的方法
  • 在子類中通過super.methodName()來呼叫父類方法

2. 淺拷貝&深拷貝

淺拷貝: 淺拷貝是按位拷貝物件,它會建立一個新物件,這個物件有著原始物件屬性值的一份精確拷貝。如果屬性是基本型別,拷貝的就是基本型別的值;如果屬性是引用型別,拷貝的就是記憶體地址。 深拷貝: 深拷貝會拷貝所有的屬性,並拷貝屬性指向的動態分配的記憶體。當物件和它所引用的物件一起拷貝時即發生深拷貝。深拷貝相比於淺拷貝速度較慢並且花銷較大。

對於淺拷貝來說,如果含有引用型別,那麼修改其中一個物件的引用值,那麼會影響到另外一個物件。按層級來說,淺拷貝只拷貝了第一層。對於預設的clone實現是淺拷貝。如果想要實現深拷貝,可以

  • 對物件進行序列化
  • 重寫clone方法
//序列化實現深拷貝

public class CloneUtils {
    @SuppressWarnings("unchecked")
    public static <T extends Serializable> T clone(T obj){
        T cloneObj = null;
        try {
            //寫入位元組流
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            ObjectOutputStream obs = new ObjectOutputStream(out);
            obs.writeObject(obj);
            obs.close();
            
            //分配記憶體,寫入原始物件,生成新物件
            ByteArrayInputStream ios = new ByteArrayInputStream(out.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(ios);
            //返回生成的新物件
            cloneObj = (T) ois.readObject();
            ois.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return cloneObj;
    }
}

public class Person implements Serializable{
    private static final long serialVersionUID = 2631590509760908280L;
}

public class CloneTest {
    public static void main(String[] args) {
        Person person =  new Person();
        Person person1 =  CloneUtils.clone(person);     
    }
}


參考:https://blog.csdn.net/chenssy/article/details/12952063
複製程式碼

protected void finalize()

protected void finalize() throws Throwable {}
複製程式碼

finalize()方法主要與 Java 垃圾回收機制有關,JVM準備對此對形象所佔用的記憶體空間進行垃圾回收前,將被呼叫。所以此方法並不是由我們主動去呼叫的。

wait()/notify/notifyAll

可先看java 多執行緒嚐鮮。後續會專門講多執行緒相關原始碼。