Java之Object類原始碼實現
一、引言
我們知道Java是單繼承語言,所有類的最終父節點都是Object(java.lang.Object)類,這一點與C++不同,因為C++既可以單繼承也可是多繼承。上述的所有類包括陣列這些等。
二、分析
1.結構與原始碼
public class Object { private static native void registerNatives(); static { registerNatives(); } // 獲取物件的型別 public final native Class<?> getClass(); // 獲取HashCode的方法 public native int hashCode(); // 物件的比較方法 public boolean equals(Object obj) { return (this == obj); } // 物件的克隆的方法 protected native Object clone() throws CloneNotSupportedException; // 多執行緒中喚醒單個執行緒的方法 public final native void notify(); // 多執行緒中喚醒所有執行緒的方法 public final native void notifyAll(); // 多執行緒中執行緒的等待 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 > 0) { timeout++; } wait(timeout); } // 多執行緒中執行緒等待的方法 public final void wait() throws InterruptedException { wait(0); } // GC回收前執行的方法 protected void finalize() throws Throwable { } }
注意:
1.被native修飾的方法,都是由本地實現的,通過JNI呼叫C或C++作業系統底層。
native修飾的方法是一個原生態方法,方法對應的實現不是在當前檔案,而是在用其他語言(如C和C++)實現的檔案中。Java語言本身不能對作業系統底層進行訪問和操作,但是可以通過JNI介面呼叫其他語言來實現對底層的訪問。
2.關於Object中的具體每一個方法的作用我們將在下面仔細介紹,上面的註釋就是幫助大家對其有個初步的認識。
2.分析
2.1 registerNatives()方法
// 這是native的方法 // 我們知道: // ①native的方法有本地的其他語言或作業系統來實現; // ②與此同時還要不在此類中裝載此類; // ③由於被private修飾,不能被類的外部呼叫。所以會在Object類載入時,通過其來註冊native方法 // 作用:向Object類註冊本地實現的方法。 private static native void registerNatives(); static { registerNatives(); }
2.2 getClass()方法
// 本地實現的方法
// 作用:返回一個類執行時的型別
public final native Class<?> getClass();
思考:此方法和ClassType.class獲取的Class物件有什麼區別?
例子:
public class ObjectDemo { public static void main(String[] args) { Fu fu = new Zi(); System.out.println(fu.getClass()); System.out.println(Fu.class); } static class Fu {} static class Zi extends Fu {} }
執行結果如下:
區別:getClass獲取的執行時型別;xxx.class獲取的是編譯時型別
2.3 hashCode()方法
// 作用:返回物件的雜湊碼,是 int 型別的數值
// 尤其是在集合這塊體現的
public native int hashCode();
HashCode的三個原則:
- 在程式執行時期間,只要物件的(欄位的)變化不會影響equals方法的決策結果,那麼,在這個期間,無論呼叫多少次hashCode,都必須返回同一個雜湊碼。
- 通過equals呼叫返回true 的2個物件的hashCode一定一樣。
- 通過equasl返回false 的2個物件的雜湊碼不需要不同,也就是他們的hashCode方法的返回值允許出現相同的情況。
關於HashCode的更多參考這裡:待完善
2.4 equals(Object obj)方法
// 作用:返回兩個物件是否相等
public boolean equals(Object obj) {
return (this == obj);
}
看到這裡,很自然的聯想到 “= =”和equal()的區別是什麼。通常,==用於基本型別的值是否相等或比較兩個物件型別的引用是否相同。而equals用於比較兩個物件是否相同,但是怎麼判斷這兩個物件是否相同,定義是很寬泛的。
從Object類的原始碼中可以看到:Object中 == 和equals方法是等價的。
所以,如果我們自定義物件,如果不重寫equals方法時,則呼叫的是Object的equals方法。如String中的equals的方法:
public boolean equals(Object anObject) {
if (this == anObject) {
// 如果地址引用相等,則這兩個物件一定相等
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
// String的長度相等的情況下
if (n == anotherString.value.length) {
// 逐個字元進行比較,只要有一個不相等,就直接返回false。否則就是true。
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
// 如果不是String型別,直接返回false
return false;
}
由此我們看出String重寫了equals方法:其比較的是兩個字串的內容。
Java中,關於equals方法有以下幾個必須遵循的原則:
- 自反性:對於任何非空引用值 x,x.equals(x) 都應返回 true;
- 對稱性:對於任何非空引用值 x 和 y,當且僅當 y.equals(x) 返回 true 時,x.equals(y) 才應返回 true;
- 傳遞性:對於任何非空引用值 x、y 和 z,如果 x.equals(y) 返回 true,並且 y.equals(z) 返回 true,那麼 x.equals(z) 應返回 true;
- 一致性:對於任何非空引用值 x 和 y,多次呼叫 x.equals(y) 始終返回 true 或始終返回 false,前提是物件上 equals 比較中所用的資訊沒有被修改;
- 對於任何非空引用值 x,x.equals(null) 都應返回 false
下面我們定義一個測試類:來檢測下。
public class ObjectDemo {
public static void main(String[] args) {
Person p1 = new Person("1001", "tom");
Person p2 = new Person("1001", "tom");
System.out.println(p1.equals(p2)); // true
}
static class Person {
private String id;
private String name;
public Person() {}
public Person(String id, String name) {
this.id = id;
this.name = name;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public boolean equals(Object obj) {
// 地址值相等,一定相同
if (this == obj) {
return true;
}
// 為null或不是Person型別,一定不相同
if (obj == null || !(obj instanceof Person)) {
return false;
}
Person p = (Person) obj;
// 只有當id和名字都相等,這兩個物件才算相同
if (p.getId().equals(this.id) && p.getName().equals(this.name)) {
return true;
}
return false;
}
}
}
從上面程式碼可以看出:我們定義Person相等的尺度是地址值相等或id、名字相等才算相同。
這時看起來,我們重寫的equals方法很完美啊。但是當我寫一個學生類(Student)繼承Person類時,也重寫了equals方法,錯誤就出現了。
public static void main(String[] args) {
Person p1 = new Person("1001", "tom");
Student s = new Student("1001", "tom", "909");
System.out.println(p1.equals(s)); // true
System.out.println(s.equals(p1)); // false
}
static class Student extends Person {
private String classRoom;
public Student() {}
public Student(String id, String name, String classRoom) {
super(id, name);
this.classRoom = classRoom;
}
@Override
public boolean equals(Object obj) {
// 地址值相等,一定相同
if (this == obj) {
return true;
}
// 為null或不是Person型別,一定不相同
if (obj == null || !(obj instanceof Student)) {
return false;
}
Student s = (Student) obj;
// 只有當id和名字及classRoom都相等,這兩個物件才算相同
if (super.getId().equals(s.getId()) && super.getName().equals(s.getName()) && this.classRoom.equals(s.getClassRoom())) {
return true;
}
return false;
}
public String getClassRoom() {
return classRoom;
}
public void setClassRoom(String classRoom) {
this.classRoom = classRoom;
}
}
執行結果為:
通過列印結果:Person.equals(student)為true,但是Student.equals(person)為false。這違反了equals的對稱性。
其實問題出現在instanceof關鍵字的判斷的那句。關於instanceof的用法可以參考這篇文章,總結的很好。
Student是 Person 的子類,person instanceof Student結果當然是false。這違反了對稱性原則。
實際上用 instanceof 關鍵字是做不到對稱性的要求的。這裡推薦做法是用 getClass()方法取代 instanceof 運算子。getClass() 關鍵字也是 Object 類中的一個方法,作用是返回一個物件的執行時類,具體如何使用我們將會在下面進行介紹。
public class ObjectDemo {
public static void main(String[] args) {
Person p1 = new Person("1001", "tom");
Student s = new Student("1001", "tom", "909");
System.out.println(p1.equals(s)); // true
System.out.println(s.equals(p1)); // false
}
static class Person {
private String id;
private String name;
public Person() {}
public Person(String id, String name) {
this.id = id;
this.name = name;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public boolean equals(Object obj) {
// 1.地址值相等,一定相同
if (this == obj) {
return true;
}
// 2.為null,一定不相等
if (obj == null) {
return false;
}
// 3.檢測是否是一個類,如果不是一個類直接返回false
if (getClass() != obj.getClass()) {
return false;
}
// 4.如果不是Person型別,一定不相同
if ( !(obj instanceof Person)) {
return false;
}
// 5.將obj轉為對應的型別
Person p = (Person) obj;
// 6.對物件的屬性進行比較,== 基本型別 equals是物件型別
if (p.getId().equals(this.id) && p.getName().equals(this.name)) {
return true;
}
// 6-2.如果是在子類,子類的equals方法要包含父類的equals方法。如在子類Student中
return false;
}
}
static class Student extends Person {
private String classRoom;
public Student() {}
public Student(String id, String name, String classRoom) {
super(id, name);
this.classRoom = classRoom;
}
@Override
public boolean equals(Object obj) {
// 1.地址值相等,一定相同
if (this == obj) {
return true;
}
// 2.為null,一定不相等
if (obj == null) {
return false;
}
// 3.檢測是否是一個類,如果不是一個類直接返回false
if (getClass() != obj.getClass()) {
return false;
}
// 4.如果不是Person型別,一定不相同
if ( !(obj instanceof Student)) {
return false;
}
// 5.將obj轉為對應的型別
Student s = (Student) obj;
// 6.對物件的屬性進行比較,== 基本型別 equals是物件型別
return super.equals(s) && this.classRoom.equals(s.getClassRoom());
// 6-2.如果是在子類,子類的equals方法要包含父類的equals方法。如在子類Student中
}
public String getClassRoom() {
return classRoom;
}
public void setClassRoom(String classRoom) {
this.classRoom = classRoom;
}
}
}
改造後的列印結果均為:
注意 :
- 重寫equals方法的時候,都要重寫equals方法,以保持其統一性。
- 當然getClass()的這種比較方式不是唯一性的,我們要根據自己業務的情況來定義定義物件是否相等的標準。比如說java中集合類,即使getClass這種方式也不太合適,這種類不適合指定相等的標準。
2.5 clone()方法
/**
* Creates and returns a copy of this object. The precise meaning
* of "copy" may depend on the class of the object. The general
* intent is that, for any object {@code x}, the expression:
* <blockquote>
* <pre>
* x.clone() != x</pre></blockquote>
* will be true, and that the expression:
* <blockquote>
* <pre>
* x.clone().getClass() == x.getClass()</pre></blockquote>
* will be {@code true}, but these are not absolute requirements.
* While it is typically the case that:
* <blockquote>
* <pre>
* x.clone().equals(x)</pre></blockquote>
* will be {@code true}, this is not an absolute requirement.
* <p>
* By convention, the returned object should be obtained by calling
* {@code super.clone}. If a class and all of its superclasses (except
* {@code Object}) obey this convention, it will be the case that
* {@code x.clone().getClass() == x.getClass()}.
* <p>
* By convention, the object returned by this method should be independent
* of this object (which is being cloned). To achieve this independence,
* it may be necessary to modify one or more fields of the object returned
* by {@code super.clone} before returning it. Typically, this means
* copying any mutable objects that comprise the internal "deep structure"
* of the object being cloned and replacing the references to these
* objects with references to the copies. If a class contains only
* primitive fields or references to immutable objects, then it is usually
* the case that no fields in the object returned by {@code super.clone}
* need to be modified.
* <p>
* The method {@code clone} for class {@code Object} performs a
* specific cloning operation. First, if the class of this object does
* not implement the interface {@code Cloneable}, then a
* {@code CloneNotSupportedException} is thrown. Note that all arrays
* are considered to implement the interface {@code Cloneable} and that
* the return type of the {@code clone} method of an array type {@code T[]}
* is {@code T[]} where T is any reference or primitive type.
* Otherwise, this method creates a new instance of the class of this
* object and initializes all its fields with exactly the contents of
* the corresponding fields of this object, as if by assignment; the
* contents of the fields are not themselves cloned. Thus, this method
* performs a "shallow copy" of this object, not a "deep copy" operation.
* <p>
* The class {@code Object} does not itself implement the interface
* {@code Cloneable}, so calling the {@code clone} method on an object
* whose class is {@code Object} will result in throwing an
* exception at run time.
*
* @return a clone of this instance.
* @throws CloneNotSupportedException if the object's class does not
* support the {@code Cloneable} interface. Subclasses
* that override the {@code clone} method can also
* throw this exception to indicate that an instance cannot
* be cloned.
* @see java.lang.Cloneable
*/
protected native Object clone() throws CloneNotSupportedException;
我們可以看出這是一個本地實現的方法。其作用:建立並返回此物件的一個副本。
思考:
- 為什麼要有克隆?
- Java中的克隆是怎麼樣的?
- Object中的clone的方法與Cloneable介面的關係?
2.6 toString()方法
// 可以看出toString列印的是類執行時的類名 加上 物件的hash碼十六進位制表示。
// 以此來表示其在記憶體中地址值。
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
2.7 notifyAll()、wait(long timeout)、wait()等方法
2.8 finalize()方法
作用:用於垃圾回收,一般由 JVM 自動呼叫,一般不需要程式設計師去手動呼叫該方法。一般會在物件被垃圾回收器清理前呼叫此方法。
三、結語
- 學習Object類,有利於我們進一步的理解OOP的思想。
- 重點理解equals、clone等方法的使用和意義。