Java反射機制基礎詳解

jopen 10年前發布 | 282K 次閱讀 Java Java開發

1.    ji什么是Java的反射?

       要讓Java程序能夠運行,就得讓Java類被Java虛擬機加載。Java類如果不被Java虛擬機加載就不能正常運行。正常情況下,我們運行的所有的程序在編譯期時候就已經把那個類被加載了。

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

2.    Java反射的作用?

假如有兩個程序員,一個程序員在寫程序的時需要使用第二個程序員所寫的類,但第二個程序員并沒完成他所寫的類。那么第一個程序員的代碼是不能通過編譯的。此時,利用Java反射的機制,就可以讓第一個程序員在沒有得到第二個程序員所寫的類的時候,來完成自身代碼的編譯。

 

Java的反射機制它知道類的基本結構,這種對Java類結構探知的能力,我們稱為Java類的“自審”。如eclipse中,一按點,編譯工具就會自動的把該對象能夠使用的所有的方法和屬性全部都列出來,供用戶進行選擇。這就是利用了Java反射的原理,是對我們創建對象的探知、自審。

3.    Class

       要正確使用Java反射機制就得使用java.lang.Class 這個類。它是Java反射機制的起源。當一個類被加載以后,Java虛擬機就會自動產生一個Class對象。通過這個Class對象我們就能獲得加載到虛擬機當中這個Class對象對應的方法、成員以及構造方法的聲明和定義等信息。

4.    反射API

       u反射API用于反應在當前Java虛擬機中的類、接口或者對象信息

u功能
1) 獲取一個對象的類信息.

          2) 獲取一個類的訪問修飾符、成員、方法、構造方法以及超類的信息.

          3) 檢獲屬于一個接口的常量和方法聲明.

          4) 創建一個直到程序運行期間才知道名字的類的實例.

          5) 獲取并設置一個對象的成員,這個成員的名字是在程序運行期間才知道.

  1. 檢測一個在運行期間才知道名字的對象的方法

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

 

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

 

代碼示例:

 

package com.reflection.classForName;

 

import java.lang.reflect.Field;

import java.lang.reflect.Method;

import javax.swing.JOptionPane;

/**

  *本類用于測試反射API,利用用戶輸入類的全路徑,

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

  */

public class ClassForName {

   /**

    *構造方法

    */

   public ClassForName(){

     

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

     

      try {

         Class<?> cla=Class.forName(classInfo);//根據類的全路徑進行類加載,返回該類的Class對象

        

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

        

         System.out.println("forName:"+cla);

        

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

   }

}

 

運行的時候,我們輸入java.lang.String,那么運行結果如下:

 

forName:class java.lang.String

方法有:public int java.lang.String.hashCode()

方法有:public boolean java.lang.String.equals(java.lang.Object)

方法有:public java.lang.String java.lang.String.toString()

方法有:public char java.lang.String.charAt(int)

方法有:private static void java.lang.String.checkBounds(byte[],int,int)

方法有:public int java.lang.String.codePointAt(int)

……………

    通過和個例子我們看到,類的全路徑是在程序運行的時候,由我們手動輸入的。所以虛擬機事先并不知道所要加載類的信息,這就是利用反射機制來對用戶輸入的類全路徑來對類自身的一個自審。從而探知該類所擁有的方法和屬性。

 

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

 

 

 

總結: 使用反射機制的步驟

首先: 導入java.lang.relfect 包

然后: 遵循三個步曲:
(1)獲得你想操作的類的 java.lang.Class 對象
(2)調用諸如 getDeclaredMethods 的方法
(3)使用反射API 來操作這些信息

  1. 獲得Class對象的三種方法
  2. 已經得到一個類的實例,可以使用如下方式來得到Class對象:

Class c = 對象名.getClass();

               例:TextField t = new TextField();

                      Class c = t.getClass();

                      Class s = c.getSuperclass();

  1. ?如果在編譯期知道類的名字,可以使用如下方法:

Class c = java.awt.Button.class;

       Class c = Integer.TYPE;

  1. 如果類名在編譯期不知道, 但是在運行期可以獲得, 可以使用下面的方法:

       Class c = Class.forName(str);

       注意:str是類的全路徑

 

Example:

       package com.reflection.classForName;

 

 

public class GetClassTest {

  

  

   public static void main(String[] args) throws InstantiationException, IllegalAccessException, NoSuchFieldException, SecurityException{

     

      System.out.println("測試開始");

     

      GetClassTestObj gctObj = new GetClassTestObj();

     

      Class<?extends GetClassTestObj> clazzClass = gctObj.getClass(); //同過反射機制得到類的對象

         System.out.println("clazzClass:"+clazzClass);

      GetClassTestObj gctObj2 = (GetClassTestObj)clazzClass.newInstance(); //調用無參構造方法,創建一個對象

      System.out.println("gctObj==gctOb2 :"+(gctObj == gctObj2));

      System.out.println("gctObj.getClass() == gctOb2.getClass() :"+(gctObj.getClass() == gctObj2.getClass()));

      gctObj2.print();

     

//    Field[] field=clazzClass.getDeclaredFields();

//    for(Field me : field){

//       System.out.println("shu:"+me);

//    }

   }

}

 

 

package com.reflection.classForName;

 

public class GetClassTestObj {

   static{     //靜態代碼塊,只執行一次

      System.out.println("nihao");

   }

   private String name = "chen";

   public String address = "chengdu";

  

   public void print(){

      System.out.println("name:"+name+" ,address:"+address);

   }

}

 

 

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

 

 

代碼示例:

代碼分析:

 

在進行 gctObj.getClass()的時候,實際上是對指定類進行類加載,這時候,會把靜態屬性、方法以及靜態代碼塊都加載到內存中。所以這時候會打印出"靜態代碼塊運行"。但這時候,對象卻還沒有產生。所以"構造方法運行"這幾個字不會打印。當執行cla.newInstance()的時候,就是利用反射機制將Class對象生成一個該類的一個實例。這時候對象就產生了。所以打印"構造方法運行"。當執行到GetClassTestObj gctObj = new GetClassTestObj();語句時,又生成了一個對象。但這時候類已經加載完畢,靜態的東西已經加載到內存中,而靜態代碼塊只執行一次,所以不用再去加載類,所以只會打印"構造方法運行",而"靜態代碼塊運行"不會打印。

 

 

 

 

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

 

 

代碼示例:

 

package com.reflection.constructor;

 

import java.lang.reflect.Constructor;

 

/**

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

 * 并通過類構造器對象生成該類的實例

 */

public class ConstructorTest {

 

   public static void main(String[] args) {

      try {

        

         /**

          * 第一步:加載類對象

          * 第二步:設置Class對象數組用于指定構造方法

          * 第三步:獲得Constructor構造器對象

          */

        

        

         Class clazz = Class.forName("com.reflection.constructor.Tests"); //加載指定字符串類的對象

        

         Class[] cl=new Class[]{int.class,int.class};   //設置Class對象數組,用于指定構造方法類型

        

         Constructor con=clazz.getConstructor(cl);      //獲得Constructor構造器對象。并指定構造方法類型

        

         //Object[] x={new Integer(33),new Integer(67)};   //給傳入參數賦初值

         //Object obj=con.newInstance(x);  //得到實例

        

         Objectobject = con.newInstance(33,66);

        

      }catch (Exception e) {

         e.printStackTrace();

      }

   }

}

 

class Tests{

   public Tests(int x,int y){

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

   }

}

 

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

  1. 通過反射模式,來執行Java類的方法

代碼示例:

package com.reflection.MethodInvoke;

 

   import java.lang.reflect.Method;

 

   /**

    *

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

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

    */

   public class MethodInvoke {

 

      public static void main(String[] args) {

         try {

           

            Class cla=Class.forName("javax.swing.JFrame"); //獲得窗體類的Class對象

               

            Object obj=cla.newInstance();  //生成窗體類的無參實例

            //Method methodSize=cla.getMethod("setSize", new Class[]{int.class,int.class}); //獲得窗體類的setSize方法對象,并指定該方法參數類型為int,int

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

            Method methodSize = cla.getMethod("setSize", classes);

           

            /*

             * 執行setSize()方法,并傳入一個Object[]數組對象,

             * 作為該方法參數,等同于  窗體對象.setSize(300,300);

             * 參數1:obj是類加載的一個實例對象

             * 參數2:是一個指定的參數對象

             */

            methodSize.invoke(obj, new Object[]{new Integer(600),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)。

  1. 單例模式(Singleton)

       保證在Java應用程序中,一個類Class只有一個實例存在。

如:建立目錄,數據庫連接都需要這樣的單線程操作。

目的: 節省內存空間,保證我們所訪問到的都是同一個對象。

 

       單例模式要求保證唯一 可以通過靜態變量保證單列模式的唯一性。

 

單例模式有以下兩種形式:

  1. 第一種形式:

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

    }

}

 

 

  1. 第二種形式:

 

public class Singleton {

 

    //先申明該類靜態對象

    private static Singleton instance = null;

   

    //創建一個靜態訪問器,獲得該類實例。加上同步機制,防止兩個線程同時進行對對象的創建

    public static synchronized Singleton getInstance() {

      

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

       if (instance == null){

           instance = new Singleton();

       }

       return instance;

    }

}

 

兩種形式大體上是差不多的

  1. 工廠模式(Factory)

       工廠模式:著名的Jive論壇 ,就大量使用了工廠模式。

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

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

 

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

 

我們先來了解一下軟件的生命周期: 分析、設計、編碼、調試、測試。

分析: 就是指需求分析,就是知道這個軟件要做成什么樣子,要實現什么樣的功能。

設計: 設計的時候要考慮到怎么樣高效的實現這個項目,如果讓一個項目團隊并行開發。這時候,通常先設計接口,把接口給實現接口的程序員和調用接口的程序員,在編碼的時候,兩個程序員可以互不影響的實現相應的功能,最后通過配置文件進行整合。

 

 

代碼示例:

 

/**

 *

 *定義接口

 */

package com.reflection.Factory;

/**

 * @interface: InterfaceTest

 * @author: chenliang

 * @description: 聲明一個測試接口

 */

public interface InterfaceTest {

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

}

 

實現這個接口,重寫其中定義的方法

 

接口實現方:

 

 

/**

 * @class: Test1

 * @author: chenliang

 * @description: 測試類

 */

 

package com.reflection.Factory;

 

public class Test1 implements InterfaceTest {

   @Override

   public void getName() {  //測試方法

      System.out.println("test1");

   }

}

 

 

 

/**

 * @class: Test2

 * @author: chenliang

 * @description: 測試類

 */

package com.reflection.Factory;

 

public class Test2 implements InterfaceTest {

 

   @Override

   public void getName() { //測試方法

      System.out.println("test2");

   }

}

 

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

 

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

 

工廠類,生產接口對象:

/**

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

 */

/**

 * @class: Factory

 * @author: chenliang

 * @description: 模仿創建一個工廠類

 */

 

package com.reflection.Factory;

 

import java.io.InputStream;

import java.util.Properties;

 

public class Factory{

 

  private static Properties prop=new Properties();  //創建私有的靜態的Properties對象

 

  static{  //靜態代碼塊,在創建這個類的實例之前執行,且只執行一次,用來加載配置文件

     try {

        InputStream ips = Factory.class.getClassLoader().getResourceAsStream("file.properties");  //加載配置文件

        prop.load(ips);

     } catch (Exception e) {

        e.printStackTrace();

     }

  }

 

  /**

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

   */

  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 = prop.getProperty("test");   //根據鍵,獲得值,這里的值是類的全路徑

        Class<?> c = Class.forName(classInfo);  //利用反射,生成Class對象

        Object obj = c.newInstance();  //獲得該Class對象的實例

        interfaceTest = (InterfaceTest)obj;  //將Object對象強轉為接口對象

     } catch (Exception e) {

        e.printStackTrace();

     }

     return interfaceTest; //返回接口對象

  }

}

配置文件內容:

test=com.reflection.Factory.Test1

 

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

 

調用方:

 

/**

 * @author: chenliang

 * @class: FactoryTest

 * @description: 測試工廠類

 */

 

 

package com.reflection.Factory;

 

public class FactoryTest {

 

   public static void main(String[] args) {

     

      Factory factory = Factory.getFactory(); //獲得工廠類的實例

     

      InterfaceTest interObj = factory.getInterface();  //調用獲得接口對象的方法,獲得接口對象

     

      interObj.getName();   //調用接口定義的方法

   }

}

 

 

上面的代碼就是調用方法。大家可以發現,在調用的時候,我們根本沒有管這個接口定義的方法要怎么樣去實現它,我們只知道這個接口定義這個方法起什么作用就行了。上面代碼運行結果要根據配置文件來定。如果配置文件里的內容是test= com.reflection.Factory.Test2。那么表示調用com.reflection.Factory.Tes2這個類里實現接口的方法,這時候打印“test2”。如果配置文件里的內容是test= com.reflection.Factory.Test1。那么表示調用com.reflection.Factory.Test1這個類里實現接口的方法,這時候打印“test1”。

 

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

原文地址:http://www.cnblogs.com/chenliang901114/archive/2013/04/23/javalearner.html

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!