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

*反射API用於反應在當前Java虛擬機中的類、接口或者對象信息
*功能
       —獲取一個對象的類信息.
       —獲取一個類的訪問修飾符、成員、方法、構造方法以及超類的信息.
       —檢獲屬於一個接口的常量和方法聲明.
       —創建一個直到程序運行期間才知道名字的類的實例.
       —獲取並設置一個對象的成員,甚至這個成員的名字是程序運行期間才知道.
       —檢測一個在運行期間才知道名字的對象的方法


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

    那麼如何利用反射API在運行的時候知道一個類的信息呢?

代碼示例:
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import javax.swing.JOptionPane;
/**
  *本類用於測試反射API,利用用戶輸入類的全路徑,
  *找到該類所有的成員方法和成員屬性
  */

public class 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();
       }
    }
    public static void 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
…………

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

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

使用反射機制的步驟:
*導入java.lang.relfect 包
*遵循三個步驟
第一步是獲得你想操作的類的 java.lang.Class 對象
第二步是調用諸如 getDeclaredMethods 的方法
第三步使用 反射API 來操作這些信息

獲得Class對象的方法
*如果一個類的實例已經得到,你可以使用
     【Class c = 對象名.getClass(); 】
      例: TextField t = new TextField();
           Class c = t.getClass();
           Class s = c.getSuperclass();

*如果你在編譯期知道類的名字,你可以使用如下的方法
    Class c = java.awt.Button.class;
        或者
    Class c = Integer.TYPE;

*如果類名在編譯期不知道, 但是在運行期可以獲得, 你可以使用下面的方法
        Class c = Class.forName(strg);


   這樣獲得Class類對象的方法,其實是利用反射API把指定字符串的類加載到內存中,所以也叫類加載器加載方法。這樣的話,它會把該類的靜態方法和靜態屬性,以及靜態代碼全部加載到內存中。但這時候,對象還沒有產生。所以爲什麼靜態方法不能訪問非靜態屬性和方法。因爲靜態方法和屬性產生的時機在非靜態屬性和方法之前。

代碼示例:

package com;

public class MyTest {
    public static void 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;
/**
 *
 * 本類測試反射獲得類的構造器對象,
 * 並通過類構造器對象生成該類的實例
 *
 */
public class ConstructorTest {
    public static void 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;
/**
 *
 * 本類測試反射獲得類的方法對象,
 * 並通過類對象和類方法對象,運行該方法
 *
 */
public class MethodTest {
    public static void 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;
public class Singleton {
    /*
     * 注意這是private私有的構造方法, 只供內部調用
     * 外部不能通過new的方式來生成該類的實例
     */
    private Singleton() {
    }

    /*
     * 在自己內部定義自己一個實例,是不是很奇怪?
     * 定義一個靜態的實例,保證其唯一性
     */
    private static Singleton instance = new Singleton();


    // 這裏提供了一個供外部訪問本class的靜態方法,可以直接訪問
    public static Singleton getInstance() {
           return instance;
    }
}

/**
 *測試單例模式
 */
class SingRun{
    public static void 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);
    }
}

第二種形式:
public class Singleton {
    //先申明該類靜態對象
    private static Singleton instance = null;

    //創建一個靜態訪問器,獲得該類實例。加上同步,表示防止兩個線程同時進行對象的創建
    public static synchronized Singleton getInstance() {
       //如果爲空,則生成一個該類實例
       if (instance == null){
           instance = new Singleton();
       }
       return instance;
    }
}

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

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

之前說利用接口可以將調用和實現相分離。

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

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

代碼示例:
/**
 *
 *定義接口
 */
interface InterfaceTest{
    public void getName();//定義獲得名字的方法
}
接口有了,那麼得到這個接口,進行實現編碼的程序員應該怎麼做呢?對了,實現這個接口,重寫其中定義的方法

接口實現方:
/**
 *第一個程序員書寫的,實現這個接口的類
 */
class Test1 implements InterfaceTest{
    /*
     * 根據業務,重寫方法
     */
    public void getName() {
       System.out.println("test1");
    }
}

/**
 *第二個程序員書寫的,實現這個接口的類
 */

class Test2 implements InterfaceTest{
    /*
     * 根據業務,重寫方法
     */
    public void getName() {
       System.out.println("test2");
    }
}

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

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

工廠類,生產接口對象:
/**
 * 本類爲工廠類,用於生成接口對象
 */
class Factory{
    //創建私有的靜態的Properties對象
    private static Properties pro=new Properties();

    //靜態代碼塊
    static{
       try {
           //加載配置文件
           pro.load(new FileInputStream("file.txt"));
       } catch (Exception e) {
           e.printStackTrace();
       }
    }

    /**
     * 單例模式,保證該類只有一個對象
     */
    private static Factory factory=new Factory();
    private Factory(){}
    public static Factory getFactory(){
       return factory;
    }

    /**
     * 本方法爲公有方法,用於生產接口對象
     * @return InterfaceTest接口對象
     */
    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

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

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章