淺談建立物件的兩種方式
經常使用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檔案就可以建立它的物件。這樣程式的可擴充套件性大大增強。比如我們更新一個軟體通常就是這種原理,我們必須一開始做好長遠的打算,埋下伏筆。在框架中更是大量運用這種方法,因為框架必然強調通用性和可擴充套件性。
所以說,存在即合理,使用時要結合實際來選擇。