1. 程式人生 > >淺談建立物件的兩種方式

淺談建立物件的兩種方式

     經常使用IDE不容易看出編譯和執行的明顯區別,因為像eclipse這樣的開發工具會自動進行編譯。當你建立一個類的時候就編譯成一個class檔案,在此基礎上做的修改儲存後又會觸發一次編譯。所以我們可以藉助記事本來看看什麼是執行時呼叫,來體驗一下建立物件的兩種方式。

首先來看一個例子,有以下的介面和兩個實現類:

public interface Fruit {
	public void color();
}
public class Apple implements Fruit {
	@Override
	public void color() {
		System.out.println("red");
	}
}
public class Banana implements Fruit {
	@Override
	public void color() {
		System.out.println("yello");
	}
}

1.使用new來建立一個物件。

//存在的Apple類
public class Test {
	public static void main(String[] args) {
		Fruit f1 = new Apple();
		f1.color();
	}
}

完美執行:


//使用一個不存在的pear類
public class Test {
	public static void main(String[] args) {
		Fruit f2 = new Pear();
	}
}
編譯階段就報錯:

2.使用反射來建立物件。

//存在的Banana類
public class Test {
	public static void main(String[] args) {
		try {
			Fruit f =  (Fruit) Class.forName("Banana").newInstance();
			f.color();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

執行時發現才發現Banana類不存在,所以丟擲了異常:


通過檢視發現反射不會根據需要去逐個編譯類(下面的Banana就沒有替我們編譯)


這時我們手動編譯Banana,然後再執行Test


我們再使用反射來呼叫不存在的pear

public class Test {
	public static void main(String[] args) {
		try {
			Fruit  f =  (Fruit) Class.forName("Pear").newInstance();
			f.color();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

同樣Test類編譯通過,執行時才發現要載入的class檔案不存在:

可以看出,使用反射在編譯階段不會報錯,說明它是執行時呼叫。它假設所有的相關類都存在,所以需要捕獲找不到類的異常。

使用new物件的方法來建立例項,編譯器會根據需要自動為我們編譯相關類,並在執行時載入這些類,編譯器在編譯時開啟和檢查相關class檔案。而對於反射機制來說,class檔案在編譯時是不可獲取的,所以在執行時開啟和檢查.class檔案。

在這裡是否會產生疑問:

New一個物件和使用反射的newInstance()究竟有什麼區別?

    使用new時是一個連貫的動作,載入類並完成後續的操作。而使用newInstance()時必須確保類已經載入,並且類已經連結了(即為靜態域分配儲存空間,並且如果必須的話將解析這個類建立的對其他類的所有引用)。別看分開了顯的麻煩,我們卻可以從中獲得好處,那就是在Class.forName()上做文章,這裡就變得更靈活了。我們可以建立一個介面,然後動態地傳入實現了介面的類的全限定名,這時候只要有它的.class檔案就可以建立它的物件。這樣程式的可擴充套件性大大增強。比如我們更新一個軟體通常就是這種原理,我們必須一開始做好長遠的打算,埋下伏筆。在框架中更是大量運用這種方法,因為框架必然強調通用性和可擴充套件性。

    所以說,存在即合理,使用時要結合實際來選擇。