1. 程式人生 > >通俗易懂解釋java反射機制(一)

通俗易懂解釋java反射機制(一)

Java的反射機制是Java特性之一,反射機制是構建框架技術的基礎所在。靈活掌握Java反射機制,對大家以後學習框架技術有很大的幫助。

 那麼什麼是Java的反射呢?

       大家都知道,要讓Java程式能夠執行,那麼就得讓Java類要被Java虛擬機器載入。Java類如果不被Java虛擬機器載入,是不能正常執行的。現在我們執行的所有的程式都是在編譯期的時候就已經知道了你所需要的那個類的已經被載入了。

Java的反射機制是在編譯並不確定是哪個類被載入了,而是在程式執行的時候才載入、探知、自審。使用在編譯期並不知道的類。這樣的特點就是反射。

 那麼Java反射有什麼作用呢?

假如我們有兩個程式設計師,一個程式設計師在寫程式的時候,需要使用第二個程式設計師所寫的類,但第二個程式設計師並沒完成他所寫的類。那麼第一個程式設計師的程式碼能否通過編譯呢?這是不能通過編譯的。利用Java反射的機制,就可以讓第一個程式設計師在沒有得到第二個程式設計師所寫的類的時候,來完成自身程式碼的編譯。

 Java的反射機制它知道類的基本結構,這種對Java類結構探知的能力,我們稱為Java類的“自審”。大家都用過Jcreator和eclipse。當我們構建出一個物件的時候,去呼叫該物件的方法和屬性的時候。一按點,編譯工具就會自動的把該物件能夠使用的所有的方法和屬性全部都列出來,供使用者進行選擇。這就是利用了Java反射的原理,是對我們建立物件的探知、自審。

Class類

要正確使用Java反射機制就得使用java.lang.Class這個類。它是Java反射機制的起源。當一個類被載入以後,Java虛擬機器就會自動產生一個Class物件。通過這個Class物件我們就能獲得載入到虛擬機器當中這個Class物件對應的方法、成員以及構造方法的宣告和定義等資訊。

 反射API

u反射API用於反應在當前Java虛擬機器中的類、介面或者物件資訊

u功能獲取一個物件的類資訊.

       —獲取一個類的訪問修飾符、成員、方法、構造方法以及超類的資訊.

       —檢獲屬於一個介面的常量和方法宣告.

       —建立一個直到程式執行期間才知道名字的類的例項.

       —獲取並設定一個物件的成員,甚至這個成員的名字是在程式執行期間才知道.

       —檢測一個在執行期間才知道名字的物件的方法

    利用Java反射機制我們可以很靈活的對已經載入到Java虛擬機器當中的類資訊進行檢測。當然這種檢測在對執行的效能上會有些減弱,所以什麼時候使用反射,就要靠業務的需求、大小,以及經驗的積累來決定。

       那麼如何利用反射API在執行的時候知道一個類的資訊呢?

 程式碼示例:

import java.lang.reflect.Field;

import java.lang.reflect.Method;

import javax.swing.JOptionPane;

/**

  *本類用於測試反射API,利用使用者輸入類的全路徑,

*找到該類所有的成員方法和成員屬性

  */

publicclass MyTest {

    /**

     *構造方法

     */

public MyTest(){

       String classInfo=JOptionPane.showInputDialog(null,"輸入類全路徑");//要求使用者輸入類的全路徑

try {

           Class cla=Class.forName(classInfo);//根據類的全路徑進行類載入,返回該類的Class物件

           Method[] method=cla.getDeclaredMethods();//利用得到的Class物件的自審,返回方法物件集合

for(Method me:method){//遍歷該類方法的集合

              System.out.println(me.toString());//列印方法資訊

           }

           System.out.println("********");

           Field[] field=cla.getDeclaredFields();//利用得到的Class物件的自審,返回屬性物件集合

for(Field me:field){ //遍歷該類屬性的集合

              System.out.println(me.toString());//列印屬性資訊

           }

       } catch (ClassNotFoundException e) {

           e.printStackTrace();

       }

    }

publicstaticvoid main(String[] args) {

new MyTest();

    }

}

執行的時候,我們輸入javax.swing.JFrame,那麼執行結果如下:

public void javax.swing.JFrame.remove(java.awt.Component)

public void javax.swing.JFrame.update(java.awt.Graphics)

…………

********

public static final int javax.swing.JFrame.EXIT_ON_CLOSE

private int javax.swing.JFrame.defaultCloseOperation

…………

大家可以發現,類的全路徑是在程式執行的時候,由使用者輸入的。所以虛擬機器事先並不知道所要載入類的資訊,這就是利用反射機制來對使用者輸入的類全路徑來對類自身的一個自審。從而探知該類所擁有的方法和屬性。

 通過上面程式碼,大家可以知道編譯工具為什麼能夠一按點就能列出使用者當前物件的屬性和方法了。它是先獲得使用者輸入物件的字串,然後利用反射原理來對這樣的類進行自審,從而列出該類的方法和屬性。

使用反射機制的步驟:

u匯入java.lang.relfect 

u遵循三個步驟第一步是獲得你想操作的類的 java.lang.Class 物件第二步是呼叫諸如 getDeclaredMethods 的方法第三步使用反射API 來操作這些資訊

 獲得Class物件的方法

u如果一個類的例項已經得到,你可以使用

Class c = 物件名.getClass(); 

例: TextField t = new TextField();

              Class c = t.getClass();

              Class s = c.getSuperclass();

u如果你在編譯期知道類的名字,你可以使用如下的方法

Class c = java.awt.Button.class; 
或者

         Class c = Integer.TYPE;

u如果類名在編譯期不知道但是在執行期可以獲得你可以使用下面的方法

Class c = Class.forName(strg);

package

publicclass MyTest {

publicstaticvoid main(String[] args) {

       TestOne  one=null;

try{

Class  cla=Class.forName("com.TestOne");//進行com.TestOne類載入,返回一個Class物件

       System.out.println("********");

       one=(TestOne)cla.newInstance();//產生這個Class類物件的一個例項,呼叫該類無參的構造方法,作用等同於new TestOne()

       }catch(Exception e){

           e.printStackTrace();

       }

       TestOne two=new TestOne();

  System.out.println(one.getClass() == two.getClass());//比較兩個TestOne物件的Class物件是否是同一個物件,在這裡結果是true。說明如果兩個物件的型別相同,那麼它們會有相同的Class物件

    }

}

class TestOne{

static{

       System.out.println("靜態程式碼塊執行");

    }

    TestOne(){

       System.out.println("構造方法");

    }

}

靜態程式碼塊執行

***********

構造方法

構造方法

Class.forName("com.TestOne")的時候,實際上是對com.TestOne進行類載入,這時候,會把靜態屬性、方法以及靜態程式碼塊都載入到記憶體中。所以這時候會打印出"靜態程式碼塊執行"。但這時候,物件卻還沒有產生。所以"構造方法"這幾個字不會列印。當執行cla.newInstance()的時候,就是利用反射機制將Class物件生成一個該類的一個例項。這時候物件就產生了。所以列印"構造方法"。當執行到TestOne two=new TestOne()語句時,又生成了一個物件。但這時候類已經載入完畢,靜態的東西已經載入到記憶體中,而靜態程式碼塊只執行一次,所以不用再去載入類,所以只會列印"構造方法",而"靜態程式碼塊執行"不會列印。

 反射機制不但可以例出該類物件所擁有的方法和屬性,還可以獲得該類的構造方法及通過構造方法獲得例項。也可以動態的呼叫這個例項的成員方法。

 程式碼示例:

package reflect;

import java.lang.reflect.Constructor;

/**

*

*本類測試反射獲得類的構造器物件,

*並通過類構造器物件生成該類的例項

*

*/

publicclass ConstructorTest {

 publicstaticvoid main(String[] args) {

try {

//獲得指定字串類物件

           Class cla=Class.forName("reflect.Tests");

//設定Class物件陣列,用於指定構造方法型別

           Class[] cl=new Class[]{int.class,int.class};

//獲得Constructor構造器物件。並指定構造方法型別

           Constructor con=cla.getConstructor(cl);

//給傳入引數賦初值

           Object[] x={new Integer(33),new Integer(67)};

//得到例項

           Object obj=con.newInstance(x);

       } catch (Exception e) {

           e.printStackTrace();

       }

    }

}

class Tests{

public Tests(int x,int y){

       System.out.println(x+"    "+y);

    }

}

執行的結果是” 33    67。說明我們已經生成了Tests這個類的一個物件。

同樣,也可以通過反射模式,來執行Java類的方法

程式碼示例:

package reflect;

import java.lang.reflect.Method;

/**

*

*本類測試反射獲得類的方法物件,

*並通過類物件和類方法物件,執行該方法

*

*/

publicclass MethodTest {

publicstaticvoid main(String[] args) {

try {

//獲得窗體類的Class物件

           Class cla=Class.forName("javax.swing.JFrame");

//生成窗體類的例項

           Object obj=cla.newInstance();

//獲得窗體類的setSize方法物件,並指定該方法引數型別為int,int

           Method methodSize=cla.getMethod("setSize"new Class[]{int.class,int.class});

/*

            * 執行setSize()方法,並傳入一個Object[]陣列物件,

            * 作為該方法引數,等同於  窗體物件.setSize(300,300);

            */

           methodSize.invoke(obj, new Object[]{new Integer(300),new Integer(300)});

//獲得窗體類的setSize方法物件,並指定該方法引數型別為boolean

           Method methodVisible=cla.getMethod("setVisible"new Class[]{boolean.class});

/*

            * 執行setVisible()方法,並傳入一個Object[]陣列物件,              *作為該方法引數。 等同於  窗體物件.setVisible(true);

            */

           methodVisible.invoke(obj, new Object[]{new Boolean(true)});

       } catch (Exception e) {

           e.printStackTrace();

       }

    }

}

反射技術大量用於Java設計模式和框架技術,最常見的設計模式就是工廠模式(Factory)和單例模式(Singleton)

單例模式(Singleton)

       這個模式主要作用是保證在Java應用程式中,一個類Class只有一個例項存在。在很多操作中,比如建立目錄 資料庫連線都需要這樣的單執行緒操作。這樣做就是為了節省記憶體空間,保證我們所訪問到的都是同一個物件。

       單例模式要求保證唯一,那麼怎麼樣才能保證唯一性呢?對了,這就是靜態變數。單例模式有以下兩種形式:

第一種形式:

package reflect;

publicclass Singleton {

/*

     * 注意這是private私有的構造方法, 只供內部呼叫

     * 外部不能通過new的方式來生成該類的例項

     */

private Singleton() {

    }

/*

     * 在自己內部定義自己一個例項,是不是很奇怪?

     * 定義一個靜態的例項,保證其唯一性

     */

privatestatic Singleton instance = new Singleton();

// 這裡提供了一個供外部訪問本class的靜態方法,可以直接訪問

publicstatic Singleton getInstance() {

returninstance;

    }

}

/**

 *測試單例模式

 */

class SingRun{

publicstaticvoid main(String[] args){

//這樣的呼叫不被允許,因為構造方法是私有的。

//Singleton x=new Singleton();

//得到一個Singleton類例項

       Singleton x=Singleton.getInstance();

//得到另一個Singleton類例項

       Singleton y=Singleton.getInstance();

//比較x和y的地址,結果為true。說明兩次獲得的是同一個物件

       System.out.println(x==y);

    }

}

第二種形式:

publicclass Singleton {

//先申明該類靜態物件

privatestatic Singleton instance = null;

//建立一個靜態訪問器,獲得該類例項。加上同步,表示防止兩個執行緒同時進行物件的建立

publicstaticsynchronized Singleton getInstance() {

//如果為空,則生成一個該類例項

if (instance == null){

instance = new Singleton();

       }

returninstance;

    }

}

工廠模式(Factory

        工廠模式是我們最常用的模式了,著名的Jive論壇 ,就大量使用了工廠模式,工廠模式在Java程式系統可以說是隨處可見。

為什麼工廠模式是如此常用?是因為工廠模式利用Java反射機制和Java多型的特性可以讓我們的程式更加具有靈活性。用工廠模式進行大型專案的開發,可以很好的進行專案並行開發。就是一個程式設計師和另一個程式設計師可以同時去書寫程式碼,而不是一個程式設計師等到另一個程式設計師寫完以後再去書寫程式碼。其中的粘合劑就是介面和配置檔案。

之前說利用介面可以將呼叫和實現相分離。

那麼這是怎麼樣去實現的呢?工廠模式可以為我們解答。

 我們先來回顧一下軟體的生命週期,分析、設計、編碼、除錯與測試。其中分析就是指需求分析,就是知道這個軟體要做成什麼樣子,要實現什麼樣的功能。功能知道了,這時就要設計了。設計的時候要考慮到怎麼樣高效的實現這個專案,如果讓一個專案團隊並行開發。這時候,通常先設計介面,把介面給實現介面的程式設計師和呼叫介面的程式設計師,在編碼的時候,兩個程式設計師可以互不影響的實現相應的功能,最後通過配置檔案進行整合。

程式碼示例:

/**

*

*定義介面

*/

interface InterfaceTest{

publicvoid getName();//定義獲得名字的方法

}

介面有了,那麼得到這個介面,進行實現編碼的程式設計師應該怎麼做呢?對了,實現這個介面,重寫其中定義的方法

 介面實現方:

/**

*第一個程式設計師書寫的,實現這個介面的類

*/

class Test1 implements InterfaceTest{

/*

     * 根據業務,重寫方法

     */

publicvoid getName() {

       System.out.println("test1");

    }

}

/**

*第二個程式設計師書寫的,實現這個介面的類

*/

class Test2 implements InterfaceTest{

/*

     * 根據業務,重寫方法

     */

publicvoid getName() {

       System.out.println("test2");

    }

}

大家可以發現,當介面定義好了以後,不但可以規範程式碼,而且可以讓程式設計師有條不紊的進行功能的實現。實現介面的程式設計師根本不用去管,這個類要被誰去呼叫。

 那麼怎麼能獲得這些程式設計師定義的物件呢?在工廠模式裡,單獨定義一個工廠類來實現物件的生產,注意這裡返回的介面物件。

工廠類,生產介面物件:

/**

*本類為工廠類,用於生成介面物件

*/

class Factory{

//建立私有的靜態的Properties物件

privatestatic Properties pro=new Properties();

//靜態程式碼塊

static{

try {

//載入配置檔案

pro.load(new FileInputStream("file.txt"));

       } catch (Exception e) {

           e.printStackTrace();

       }

    }

/**

*單例模式,保證該類只有一個物件

*/

privatestatic Factory factory=new Factory();

private Factory(){}

publicstatic Factory getFactory(){

returnfactory;

    }

/**

*本方法為公有方法,用於生產介面物件

*@returnInterfaceTest介面物件

*/

public  InterfaceTest getInterface(){

       InterfaceTest interfaceTest=null;//定義介面物件

try {

//根據鍵,獲得值,這裡的值是類的全路徑

           String classInfo=pro.getProperty("test");

//利用反射,生成Class物件

           Class c=Class.forName(classInfo);

//獲得該Class物件的例項

           Object obj=c.newInstance();

//將Object物件強轉為介面物件

           interfaceTest=(InterfaceTest)obj;

       } catch (Exception e) {

           e.printStackTrace();

       }

//返回介面物件

return interfaceTest;

    }

}

配置檔案內容:

test=factory.Test2

通過這個類,大家可以發現,在呼叫的時候,得到的是個介面物件。而一個介面變數可以指向實現了這個介面的類物件。在利用反射的時候,我們並沒有直接把類的全路徑寫出來,而是通過鍵獲得值。這樣的話,就有很大的靈活性,只要改變配置檔案裡的內容,就可以改變我們呼叫的介面實現類,而程式碼不需做任何改變。在呼叫的時候,我們也是通過介面呼叫,甚至我們可以連這個介面實現類的名字都不知道。

呼叫方:

publicclass FactoryTest {

publicstaticvoid main(String[] args) {

//獲得工廠類的例項

       Factory factory=Factory.getFactory();

//呼叫獲得介面物件的方法,獲得介面物件

       InterfaceTest inter=factory.getInterface();

//呼叫介面定義的方法

       inter.getName();

    }

}

上面的程式碼就是呼叫方法。大家可以發現,在呼叫的時候,我們根本沒有管這個介面定義的方法要怎麼樣去實現它,我們只知道這個介面定義這個方法起什麼作用就行了。上面程式碼執行結果要根據配置檔案來定。如果配置檔案裡的內容是test=factory.Test2。那麼表示呼叫factory.Test2這個類裡實現介面的方法,這時候列印“test2”。如果配置檔案裡的內容是test=factory.Test1。那麼表示呼叫factory.Test1這個類裡實現介面的方法,這時候列印“test1”。

反射機制是框架技術的原理和核心部分。通過反射機制我們可以動態的通過改變配置檔案(以後是XML檔案)的方式來載入類、呼叫類方法,以及使用類屬性。這樣的話,對於編碼和維護帶來相當大的便利。在程式進行改動的時候,也只會改動相應的功能就行了,呼叫的方法是不用改的。更不會一改就改全身。