深入理解Object類

衆所周知 java 是一門面向對象的高級語言,除了基本類型之外,其他的所有類(包括枚舉類 enum)都繼承於基類,也就是 Object 類。所以深入瞭解 java 語言的類特徵,Object 類的基本方法是非常有必要的。
下面請先看幾個問題,來檢測你對 Object 類瞭解多少。知不足,方可改之。

1. Object 類中有哪些方法

算上構造方法,Object 基類中共有13種方法

void registerNatives(); //私有方法 無須關心
// 獲取當前對象的類型信息
native Class<?> getClass();
// native方法 一般自定義的類使用時需要重寫
int hashCode();
// 判斷兩個對象是否相等 默認爲 ==
// 一般重寫hashCode也重寫equals方法
boolean equals(Object obj);
// 對象拷貝方法 使用需重寫
Object clone();
// print()打印時和字符串連接時會自動調用
String toString();
// 重寫後第一次垃圾回收時執行的方法
void finalize();
// 接下來的方法都爲同步方法
// 只有在synchronized同步條件下才能調用
void notify();    // 線程調度器喚醒一個線程
void notifyAll(); // 喚醒所有等待中的線程
// wait方法使當前線程等待,並釋放同步鎖
// 直到其他線程調用此對象的notify或notifyAll方法被喚醒
// 或者等待了指定時間後,停止等待,進入就緒狀態
void wait(long timeout);
void wait(long timeout, int nanos);
void wait(); // 不指定時間,只能被其他線程喚醒

2. Object 類中哪些方法被標註爲 native,是什麼意思

衆所周知,java 是運行在 JVM 上的語言,它擁有一次編寫,到處執行,以及不用考慮底層處理的優勢。
既然 java 語言本身無須考慮底層,那麼自然就需要另一種語言,比如 c,c++,來完成對底層的處理。或者對於一些十分重視性能的方法,也需要一些更加貼合硬件的語言來編寫。
因而,java 需要調用一些其它語言所書寫的代碼,Java Native Interface (JNI) 也就因此誕生了。
而 native,就是用來告知 JVM,該方法的調用在外部,讓它根據本地方法執行。
而我們只需要關心這些方法的作用是什麼,至於是如何實現的,我們則就無須關心了。
其中,registerNatives(),getClass(),hashCode(),clone(),notify(),notifyAll(),wait(),finalize(),都是 native 方法

void registerNatives();
native Class<?> getClass();
int hashCode();
Object clone();
void finalize();
void notify();
void notifyAll();
void wait(long timeout);
void wait(long timeout, int nanos);
void wait();

3. 如果不重寫 equals 方法,那它的判定條件是什麼

判定條件是 ==

public boolean equals(Object obj) {
    return (this == obj);
}

4. 基類中原 hashCode 方法是如何實現的

是 native 方法,所以一般自定義類時,需要使用 hashCode() 方法,一般重寫

5. 重寫 hashCode 方法,是否需要重寫 equals 方法,爲什麼

一般 hashCode() 方法是在 HashMap,HashSet,ConcurrentHashMap 等等集合中使用的,對於重複對象的判定方法是,既要 HashCode() 值相等,又需要 equals() 方法返回 true,所以重寫 hashCode() 和 equals() 方法時,要保證 hashCode() 值相等時 equals() 方法返回 true,hashCode() 值不等時 equals() 方法返回 false。

6. 重寫 equals() 方法需要遵循哪些規範

  1. 自反性:任何對象和其自身調用 equals() 方法一定返回 true。
    a.equals(a) 返回 true。
  2. 對稱性:任意兩個對象,互相調用 equals() 方法一定同爲 true 或 false。
    a.equals(b) 返回 true,則b.equals(a) 返回 true。
    a.equals(b) 返回 false,則b.equals(a) 返回 false。
  3. 傳遞性:如果一個對象與其它兩個對象調用 equals(方法) 返回 true,則那兩個對象調用 equals() 方法也一定返回true。
    a.equals(b) 返回 true,a.equals© 返回 true,則 b.equals© 返回 true
  4. 一致性:對於任意兩個對象,在自身沒有改變的情況下,多次調用 equals() 方法的返回值必須相同
  5. 對於任意對象,其對 null 調用 equals() 方法返回值必須爲 false。
// 舉例說明 一般equals()方法這麼重寫
class Element {
	int attribute; // 對象屬性 爲方便描述例子沒有寫爲private
	public boolean equals(Object obj) { // 參數類型爲Object
		if(null == obj) // 規則5,必須返回false
			return false;
		if(this == obj) // 規則1,必須返回true
			return true;
		/**
		* 對於instanceof這種寫法,一般子類不作方法重寫
		* 這種情況子類可以當做它的父類進行equals比較
		* !!! 如果子類要進行重寫,一般說明子類不希望與父類相等
		* 則父子都要用getClass()來進行類型判斷
		*/
		if(obj instanceof Element) {
			// 在是同一種類時強轉類型
			Element element = (Element) obj;
			// 然後一般比較對象屬性
			if(this.attribute == element.attribute)
				return true;
			return false; // 屬性不相同返回false
		}
		return false; // 不屬於同一個類一般返回false
	}
}

7. 任意子類繼承的 clone 方法能否被調用,爲什麼

不能,因爲 clone() 方法是 protected 修飾的,只能被子類繼承,外部一般無法調用

8. clone 方法是什麼,如何使用

clone,顧名思義,就是克隆的意思,在 java 中就是複製對象的作用。
由於 clone() 方法默認可見性是 protected,外部無法調用,因此一般實現可複製對象需要
1、實現 Cloneable 接口,作爲標誌接口。
2、重寫clone()方法,使可見性提升爲public。
衆所周知,java 中有不止一種創建對象的機制,new,反射創建,clone() 方法複製創建。
clone() 方法與 new 創建對象類似,都是先分配內存,之後在給屬性賦值,只不過裏面個屬性的值是原對象中賦值過來的。
對於默認的拷貝方式,也就是淺拷貝,拷貝方式都是值傳遞。

class A implements Cloneable {
	public Object clone() throws CloneNotSupportedException {
		return super.clone();
	}
}
// 賦值過程如同下面的代碼
b.x = a.x; b.y = a.y; b.z = a.z;

我們知道,java 中基本類型使用值傳遞沒有問題,但對於非基本類型的對象,保存對象的變量都是存儲的對象引用,因而值傳遞時傳遞過去的都是對象的引用,所以對於對象中的非基本類型的對象屬性,拷貝後的對象與原對象還是公用同一個對象。
舉個小例子

public class A implements Cloneable {
	private String str = "I'm a string.";
	public Object clone() throws CloneNotSupportedException {
		return super.clone();
	}
	public static void main(String[] ars) throws CloneNotSupportedException {
		A a1 = new A();
		A a2 = (A) a1.clone();
		System.out.println(a1.str == a2.str);
	}
}

輸出 true,也就是說拷貝前後的兩個 String 對象是同一個對象,內部 String 對象並沒有被複制。
要實現深拷貝,則必須對 clone() 方法進行一定程度的重寫。
可以首先調用 super.clone() 方法完成對基本類型的拷貝,然後對每個非基本類型對象也要進行一次 clone() 拷貝。

public class A implements Cloneable {
	private B b = new B();
	public Object clone() throws CloneNotSupportedException {
		A a = (A) super.clone();  //先淺拷貝一個對象
		// 再對每一個非基本類型對象也進行clone()拷貝
		a.b = (B) this.b.clone();
		return a;
	}
	public static void main(String[] ars) throws CloneNotSupportedException {
		A a1 = new A();
		A a2 = (A) a1.clone();
		System.out.println(a1.b == a2.b);
	}
}
class B implements Cloneable {
	public Object clone() throws CloneNotSupportedException {
		return super.clone();
	}
}

輸出 false。
由此我們看出拷貝出的對象中的非基本類型也屬性也是屬於不同的對象,從而完成深拷貝。
總結得出,如果要深拷貝,需要被複制對象的繼承鏈引用鏈上的每一個對象都實現克隆機制。

9. toString 方法的作用

toString() 方法是用來描述一個對象的方法,它將返回一個字符串來描述對象信息,在執行 print() 方法或者與字符串相加時會自動調用該方法。
由於基類的 toString() 方法默認返回對象類名稱和@內存地址,我們要這一串字符信息是沒有作用的,所以一般需要對對象信息加以描述表示時,需要重寫 toString() 方法,按照我們需要的表示方式進行表示。

10. wait 方法是什麼

  • wait() 方法是同步塊中纔可以使用的方法,或者在同步方法中可以調用的方法。
    synchronized 關鍵字可以修飾方法,也可以修飾任意對象,也可以修飾類,在synchronized 代碼塊中,會鎖定一個對象(或類,因爲類是一種特殊的對象),只有當前線程能夠操作代碼塊中的內容,其他線程會無法獲取到對該對象的鎖定,因而代碼無法執行下去,直到該線程搶到該對象的鎖,才能執行到代碼。
  • 在 synchronized 代碼塊中,調用 wait() 方法,將會釋放掉當前線程對該對象的鎖,並進入等待池中等待,直到有一個線程調用 notify() 或 notifyAll() 方法喚醒後纔會進入就緒狀態,並進入鎖池繼續搶鎖,搶鎖成功後繼續向下執行代碼。
  • wait 方法有3種重載,一種是無參,一種是1個參數,一種是2個參數。其中 wait() 方法等同於 wait(0) 等同於 wait(0, 0) 方法,它們都表示等待的時間無限長,只有notify 方法才能夠將其喚醒。另外兩種傳入的參數第一個數字表示毫秒,如果要傳入第二個參數,則表示納秒,線程可以被 notify 喚醒,也可以在等待指定時間之後自動喚醒,防止永久等待的情況發生。

11. notify 方法是什麼

  • notify() 和 notifyAll() 也是同步塊中纔可以調用的方法,notify() 方法會根據線程調度喚醒一個線程進入鎖池進行搶鎖,至於喚醒哪一個線程,不是程序員能手動控制的,因此具有隨機性。所以有些時候爲了使特定線程能夠執行,則需喚醒所有線程共同進入鎖池搶鎖。
  • notifyAll() 方法則會喚醒所有線程進入鎖池進行搶鎖執行,但是最終終究只有一個線程,或者這些線程中沒有一個能夠獲取對該對象的同步鎖,因此在併發量高的情況下,會對性能造成影響,如果沒有對哪個線程有特殊的要求,可以使用 notify() 方法,只喚醒一個線程,並且自身釋放鎖,給該線程機會獲取鎖,則可以避免大量線程爭搶的情況出現。

12. finalize 方法是什麼

  • finalize() 方法是在GC(垃圾回收)時纔有可能調用的代碼。在要回收某個不可達對象之前,會先判斷該對象的 finalize() 方法是否被重寫,如果沒有,則直接回收,如果被重寫了,則會調用該方法。所以,這個方法可以實現在對象即將被垃圾回收時自救的作用,即在該方法中使自身被重新引用,避免被回收。但該 finalize() 方法只會被調用一次。在第一次GC時如果調用了該方法併成功自救,第二次GC時不會再調用該方法,該對象會被直接回收。
  • 但是,在 java 中,由於GC自動回收機制,不能保證 finalize() 方法的執行時間,也不能保證是否會被執行(自始至終未進行垃圾回收)。所以該方法具有不確定性,從程序開始執行到調用該方法之間的時間是任意不可控的。而且重寫 finalize() 方法會延長垃圾回收時間,因而不推薦重寫該方法。
  • 有些書籍或者博客會建議在 finalize() 方法中做資源的回收處理,這不是可取的,由於 finalize() 方法存在不確定性,不能保證資源的及時釋放,甚至至始自終不釋放。因此,我們通常需要手動調用 close() 方法釋放資源
// 自救代碼 僅做參考 不推薦在任何地方重寫該方法
public class Test {
	static Test test;
	public void finalize() {
		test = this;
		System.out.println("我成功自救了");
	}
	public static void main(String[] ars) throws InterruptedException {
		test = new Test();
		test = null;
		System.gc();
		Thread.sleep(1000);// 等待gc執行完畢
		System.out.println(test);
		test = null;
		System.gc();
		Thread.sleep(1000);// 等待gc執行完畢
		System.out.println(test);
	}
}

我成功自救了
test.Test@15db9742
null

由此可見,在第一次GC時,該對象成功建立了靜態變量 test 的引用,第二次GC時,將不會調用該方法,將被徹底回收。
Object 基類源碼

package java.lang;
public class Object {
    private static native void registerNatives();
    static {
        registerNatives();
    }
    public final native Class<?> getClass();
    public native int hashCode();
    public boolean equals(Object obj) {
        return (this == obj);
    }
    protected native Object clone() throws CloneNotSupportedException;
    public String toString() {
        return getClass().getName() + "@" + Integer.toHexString(hashCode());
    }
    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);
    }
    protected void finalize() throws Throwable { }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章