1. 程式人生 > >Java之Object類原始碼實現

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的三個原則:

  1. 在程式執行時期間,只要物件的(欄位的)變化不會影響equals方法的決策結果,那麼,在這個期間,無論呼叫多少次hashCode,都必須返回同一個雜湊碼。
  2. 通過equals呼叫返回true 的2個物件的hashCode一定一樣。
  3. 通過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方法有以下幾個必須遵循的原則:

  1. 自反性:對於任何非空引用值 x,x.equals(x) 都應返回 true;
  2. 對稱性:對於任何非空引用值 x 和 y,當且僅當 y.equals(x) 返回 true 時,x.equals(y) 才應返回 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
    下面我們定義一個測試類:來檢測下。
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;
        }
    }
}

改造後的列印結果均為:
在這裡插入圖片描述
注意 :

  1. 重寫equals方法的時候,都要重寫equals方法,以保持其統一性。
  2. 當然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 自動呼叫,一般不需要程式設計師去手動呼叫該方法。一般會在物件被垃圾回收器清理前呼叫此方法。

三、結語

  1. 學習Object類,有利於我們進一步的理解OOP的思想。
  2. 重點理解equals、clone等方法的使用和意義。