「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
修飾的屬性和方法,在同一包下或在不同包下的子類可以訪問。顯然,CloneTest
和Object
不在同一包下,不過按照字面意思,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 多執行緒嚐鮮。後續會專門講多執行緒相關原始碼。