天天看點

java反射詳解

轉自:http://blog.csdn.net/zhuyu_deng/article/details/9769665

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,利用使用者輸入類的全路徑,

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

  */

publicclassmytest {

    /**

     *構造方法

     */

    publicmytest(){

       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();

       }

    }

    public static voidmain(string[]

args) {

       newmytest();

}

運作的時候,我們輸入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

public classmytest {

    public staticvoid

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(exceptione){

       testone two=newtestone();

  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對象生成一個該類的一個執行個體。這時候對象就産生了。是以列印"構造方法"。當執行到testonetwo=new

testone()語句時,又生成了一個對象。但這時候類已經加載完畢,靜态的東西已經加載到記憶體中,而靜态代碼塊隻執行一次,是以不用再去加載類,是以隻會列印"構造方法",而"靜态代碼塊運作"不會列印。

反射機制不但可以列出該類對象所擁有的方法和屬性,還可以獲得該類的構造方法及通過構造方法獲得執行個體。也可以動态的調用這個執行個體的成員方法。

package reflect;

import java.lang.reflect.constructor;

 *

 *本類測試反射獲得類的構造器對象,

 *并通過類構造器對象生成該類的執行個體

 */

publicclass

constructortest {

    public static void

main (string[] args) {

           //獲得指定字元串類對象

           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),newinteger(67)};

           //得到執行個體

           objectobj=con.newinstance(x);

       }catch(exception e) {

class tests{

    publictests(int

x,int y){

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

運作的結果是” 33    67”。說明我們已經生成了tests這個類的一個對象。

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

 *本類測試反射獲得類的方法對象,

 *并通過類對象和類方法對象,運作該方法

publicclassmethodtest {

    publicstaticvoidmain(string[]

           //獲得窗體類的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[]{newinteger(300),new

integer(300)});

       //獲得窗體類的setsize方法對象,并指定該方法參數類型為boolean

           method methodvisible=cla.getmethod("setvisible",new

class[]{boolean.class});

            *執行setvisible()方法,并傳入一個object[]數組對象,              *作為該方法參數。等同于  窗體對象.setvisible(true);

           methodvisible.invoke(obj,new object[]{newboolean(true)});

反射技術大量用于java設計模式和架構技術,最常見的設計模式就是工廠模式(factory)和單例模式(singleton)。

單例模式(singleton)

       這個模式主要作用是保證在java應用程式中,一個類class隻有一個執行個體存在。在很多操作中,比如建立目錄 資料庫連接配接都需要這樣的單線程操作。這樣做就是為了節省記憶體空間,保證我們所通路到的都是同一個對象。

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

第一種形式:

public classsingleton {

    /*

     *注意這是private私有的構造方法, 隻供内部調用

     *外部不能通過new的方式來生成該類的執行個體

    private singleton() {

     *在自己内部定義自己一個執行個體,是不是很奇怪?

     *定義一個靜态的執行個體,保證其唯一性

    private static singleton

instance = new singleton();

    //這裡提供了一個供外部通路本class的靜态方法,可以直接通路

    public staticsingleton  getinstance() {

           return instance;

 *測試單例模式

class singrun{

args){

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

       //singleton x=new singleton();

       //得到一個singleton類執行個體

       singleton x=singleton.getinstance();

       //得到另一個singleton類執行個體

       singleton y=singleton.getinstance();

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

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

第二種形式:

publi classsingleton {

    //先申明該類靜态對象

    private staticsingleton

instance =null;

    //建立一個靜态通路器,獲得該類執行個體。加上同步,表示防止兩個線程同時進行對象的建立

    public static synchronized

singleton getinstance() {

       //如果為空,則生成一個該類執行個體

       if (instance

==null){

           instance =new

singleton();

       returninstance;

工廠模式(factory)

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

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

之前說利用接口可以将調用和實作相分離。那麼這是怎麼樣去實作的呢?工廠模式可以為我們解答。

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

 *定義接口

interface interfacetest{

    publicvoid

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

接口有了,那麼得到這個接口,進行實作編碼的程式員應該怎麼做呢?對了,實作這個接口,重寫其中定義的方法

接口實作方:

 *第一個程式員書寫的,實作這個接口的類

class test1implements interfacetest{

     *根據業務,重寫方法

    publicvoid getname() {

       system.out.println("test1");

 *第二個程式員書寫的,實作這個接口的類

class test2implements interfacetest{

    publicvoid 

getname() {

       system.out.println("test2");

大家可以發現,當接口定義好了以後,不但可以規範代碼,而且可以讓程式員有條不紊的進行功能的實作。實作接口的程式員根本不用去管,這個類要被誰去調用。

那麼怎麼能獲得這些程式員定義的對象呢?在工廠模式裡,單獨定義一個工廠類來實作對象的生産,注意這裡傳回的接口對象。

工廠類,生産接口對象:

 *本類為工廠類,用于生成接口對象

class factory{

    //建立私有的靜态的properties對象

    privatestatic

properties pro=new properties();

    //靜态代碼塊

           //加載配置檔案

           pro.load(new

fileinputstream("file.txt"));

     *單例模式,保證該類隻有一個對象

    private static 

factory factory=new factory();

    privatefactory(){}

    public staticfactory getfactory(){

       return  factory;

     *本方法為公有方法,用于生産接口對象

     *@returninterfacetest接口對象

    public interfacetest getinterface(){

       interfacetest   interfacetest=null;//定義接口對象

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

           string  classinfo=pro.getproperty("test");

           //利用反射,生成class對象

           class  c=class.forname(classinfo);

           //獲得該class對象的執行個體

           object   obj=c.newinstance();

           //将object對象強轉為接口對象

           interfacetest=(interfacetest)obj;

       //傳回接口對象

       return interfacetest;

配置檔案内容:

test=factory.test2

通過這個類,大家可以發現,在調用的時候,得到的是個接口對象。而一個接口變量可以指向實作了這個接口的類對象。在利用反射的時候,我們并沒有直接把類的全路徑寫出來,而是通過鍵獲得值。這樣的話,就有很大的靈活性,隻要改變配置檔案裡的内容,就可以改變我們調用的接口實作類,而代碼不需做任何改變。在調用的時候,我們也是通過接口調用,甚至我們可以連這個接口實作類的名字都不知道。

調用方:

publicclassfactorytest {

    public  static void 

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檔案)的方式來加載類、調用類方法,以及使用類屬性。這樣的話,對于編碼和維護帶來相當大的便利。在程式進行改動的時候,也隻會改動相應的功能就行了,調用的方法是不用改的。更不會一改就改全身。