天天看點

Java反射機制

Java 語言的反射機制

在Java運作時環境中,對于任意一個類,能否知道這個類有哪些屬性和方法?對于任意一個對象,能否調用它的任意一個方法?

答案是肯定的。這種動态擷取類的資訊以及動态調用對象的方法的功能來自于Java 語言的反射(Reflection)機制。

Java 反射機制主要提供了以下功能

在運作時判斷任意一個對象所屬的類。

在運作時構造任意一個類的對象。

在運作時判斷任意一個類所具有的成員變量和方法。

在運作時調用任意一個對象的方法

Reflection 是Java被視為動态(或準動态)語言的一個關鍵性質。

這個機制允許程式在運作時透過Reflection APIs取得任何一個已知名稱的class的内部資訊,

包括其modifiers(諸如public, static 等等)、superclass(例如Object)、實作之interfaces(例如Serializable),

也包括fields和methods的所有資訊,并可于運作時改變fields内容或調用methods

一般而言,動态語言定義是:“程式運作時,允許改變程式結構或變量類型,這種語言稱為動态語言”。

從這個觀點看,Perl,Python,Ruby是動态語言,C++,Java,C#不是動态語言

盡管在這樣的定義與分類下Java不是動态語言,它卻有着一個非常突出的動态相關機制:Reflection。

這個字的意思是“反射、映象、倒影”,用在Java身上指的是我們可以于運作時加載、探知、使用編譯期間完全未的classes。

換句話說,Java程式可以加載一個運作時才得知名稱的class,獲悉其完整構造(但不包括methods定義),并生成其對象實體、或對其fields設值、或喚起其methods。

這種“看透class”的能力(the ability of the program to examine itself)被稱introspection(内省、内觀、檢討)。

Reflection和introspection是常被并提的兩個術語

在JDK中,主要由以下類來實作Java反射機制,這些類都位于java.lang.reflect包中

§Class類:代表一個類。

§Field 類:代表類的成員變量(成員變量也稱為類的屬性)。

§Method類:代表類的方法。

§Constructor 類:代表類的構造方法。

§Array類:提供了動态建立數組,以及通路數組的元素的靜态方法

例程DumpMethods類示範了Reflection API的基本作用,它讀取指令行參數指定的類名,然後列印這個類所具有的方法資訊

public class DumpMethods {

 public static void main(String[] args) throws Exception {

//  加載初始化指令行參數指定的類,每一個類初始化加載時都有一個對應的Class類,這個Class類可以獲得該類的所有方法

  Class<?> classType=Class.forName(args[0]);

//  獲得類的所有方法

  Method methods[]=classType.getDeclaredMethods();

  for(int i=0;i<methods.length;i++){

   System.out.println(methods[i].toString());

  }

 }

}

運作方法:右擊-->Run AS -->Run Configurations-->Java Applivation-->DumpMethods-->Arguments-->prgram arguments-->輸入一個類(如:java.lang.String)-->Apply-->Run.

例程ReflectTester 類進一步示範了Reflection API的基本使用方法。ReflectTester類有一個copy(Object object)方法,這個方法能夠建立一個和參數object 同樣類型的對象,然後把object對象中的所有屬性拷貝到建立的對象中,并将它傳回

這個例子隻能複制簡單的JavaBean,假定JavaBean 的每個屬性都有public 類型的getXXX()和setXXX()方法。

public class ReflectTester {

 public Object copy(Object object) throws Exception {

  // 獲得對象類型,獲得Object對象所對應的Class類

  Class<?> classType = object.getClass();

  // 将Object所屬的類的名字列印出來,getName()獲得類的完整名字

  System.out.println("Class:" + classType.getName());

  // getConstructors():獲得類的public類型的構造方法。

  // getConstructor(Class[] parameterTypes):獲得類的特定構造方法,parameterTypes

  // 參數指定構造方法的參數類型。

  // 通過預設構造方法建立一個新的對象,Constructor類代表的object類的構造方法,newInstance():通過類的不帶參數的構造方法建立這個類的一個對象。

  Object objectCopy = classType.getConstructor(new Class[] {})

    .newInstance(new Object[] {});// 通過預設的構造方法來建立一個目前類的執行個體,先調用Class類的getConstructor()方法獲得一個Constructor

            // 對象,它代表預設的構造方法,然後調用Constructor對象的newInstance()方法構造一個執行個體。

  // getDeclaredFields()獲得對象的所有屬性,Field類代表object類的成員變量(成員變量也稱為類的屬性),如果是getFields()是獲得類的public類型的所有方法

  Field fields[] = classType.getDeclaredFields();// Class類的getDeclaredFields()方法傳回類的所有屬性,包括public、protected、預設和private通路級别的屬性

  // 獲得每個屬性相應的getXXX()和setXXX()方法,然後執行這些方法,把原來對象的屬性拷貝到新的對象中

  for (int i = 0; i < fields.length; i++) {

   Field field = fields[i];

   // 得到field對象所對應的屬性的名字

   String fieldName = field.getName();

   // 将fieldName的第一個字母轉化為大寫賦給firstLetter.

   String firstLetter = fieldName.substring(0, 1).toUpperCase();

   // 獲得和屬性對應的getXXX()方法的名字,将"get"+大寫的首字母+剩下的字母(從1到最後一個字母)

   String getMethodName = "get" + firstLetter + fieldName.substring(1);

   // 獲得和屬性對應的setXXX()方法的名字

   String setMethodName = "set" + firstLetter + fieldName.substring(1);

   // getMethod()獲得類的public的類型的方法,getDeclaredMethod()獲得類的所有類型的方法

   // 獲得和屬性對應的getXXX()方法,Method類代表object類的方法,getMethod(String name,

   // Class[] parameterTypes):獲得類的特定方法,name參數指定方法的名字,parameterTypes

   // 參數指定方法的參數類型。

   Method getMethod = classType.getMethod(getMethodName,

     new Class[] {});

   // 獲得和屬性對應的setXXX()方法

   Method setMethod = classType.getMethod(setMethodName,

     new Class[] { field.getType() });// 傳回設定的field屬性的Class類型

   // invoke調用原對象的getXXX()方法,第一個為參數的方法對象的名稱,每二個為參數的方法對象的參數清單

   Object value = getMethod.invoke(object, new Object[] {});// 調用object對象的Get方法

   System.out.println(fieldName + ":" + value);

   // 調用拷貝對象objectCopy的setXXX()方法

   setMethod.invoke(objectCopy, new Object[] { value });

  return objectCopy;

  Customer customer = new Customer("Tom", 21);

  customer.setId(new Long(1));

  Customer customerCopy = (Customer) new ReflectTester().copy(customer);

  System.out.println("Copy information:" + customerCopy.getId() + " "

    + customerCopy.getName() + " " + customerCopy.getAge());

class Customer {

 private Long id;

 private String name;

 private int age;

 public Customer() {

 public Customer(String name, int age) {

  this.name = name;

  this.age = age;

 public Long getId() {

  return id;

 public void setId(Long id) {

  this.id = id;

 public String getName() {

  return name;

 public void setName(String name) {

 public int getAge() {

  return age;

 public void setAge(int age) {

在例程InvokeTester類的main()方法中,運用反射機制調用一個InvokeTester對象的add()和echo()方法

public class InvokeTester {

 public int add(int param1, int param2) {

  return param1 + param2;

 public String echo(String msg) {

  return "echo: " + msg;

 // public InvokeTester(String str){

 // }

  // 獲得InvorkeTester類所對應的Class對象

  Class<?> classType = InvokeTester.class;

  // 調用不帶參數的預設構造方法生成一個執行個體

  Object invokeTester = classType.newInstance();

  // Object invokeTester = classType.getConstructor(new

  // Class[]{}).newInstance(new Object[]{});

  // 調用InvokeTester對象的add()方法

  Method addMethod = classType.getMethod("add", new Class[] { int.class,

    int.class });

  // invoke用于調用方法,Method類的invoke(Object obj,Object

  // args[])方法接收的參數必須為對象,如果參數為基本類型資料,必須轉換為相應的包裝類型的對象。invoke()方法的傳回值總是對象,如果實際被調用的方法的傳回類型是基本類型資料,那麼invoke()方法會把它轉換為相應的包裝類型的對象,再将其傳回

  // 盡管InvokeTester 類的add()方法的兩個參數以及傳回值都是int類型,調用add Method

  // 對象的invoke()方法時,隻能傳遞Integer 類型的參數,并且invoke()方法的傳回類型也是Integer

  // 類型,Integer 類是int 基本類型的包裝類

  Object result = addMethod.invoke(invokeTester, new Object[] {

    new Integer(100), new Integer(200) });

  System.out.println((Integer) result);

  // 調用InvokeTester對象的echo()方法

  Method echoMethod = classType.getMethod("echo",

    new Class[] { String.class });

  result = echoMethod.invoke(invokeTester, new Object[] { "Hello" });

  System.out.println((String) result);

java.lang.Array 類提供了動态建立和通路數組元素的各種靜态方法。例程

ArrayTester1 類的main()方法建立了一個長度為10 的字元串數組,接着把索引位置為5 的元素設為“hello”,然後再讀取索引位置為5 的元素的值

 public class ArrayTester1

{

    public static void main(String args[]) throws Exception

    {

//    classType為java.lang.String對應的Class類

        Class<?> classType = Class.forName("java.lang.String");

        // 建立一個長度為10的字元串數組,生成java.lang.String對應的一個數組,

        Object array = Array.newInstance(classType, 10);

        // 把array數組的索引位置為5的元素設為"hello"

        Array.set(array, 5, "hello");

        // 獲得array數組的索引位置為5的元素的值

        String s = (String) Array.get(array, 5);

        System.out.println(s);

    }

例程ArrayTester2 類的main()方法建立了一個 5 x 10 x 15 的整型數組,并把索引位置為[3][5][10] 的元素的值為設37

 public class ArrayTester2 {

 public static void main(String args[]) {

  int[] dims = new int[] { 5, 10, 15 };

  // Integer.TYPE獲得Integer對應的Class對象,dims決定是數組的維數及長度

  Object array = Array.newInstance(Integer.TYPE, dims);

  // 傳回數組第一維索引為3的類型的一個對象,此時為一個二維數組,第一維為3所在的一個二維數組

  Object arrayObj = Array.get(array, 3);

  // 傳回該數組的元件類型,如果不為數組的話傳回NULL

  Class<?> cls = arrayObj.getClass().getComponentType();

  System.out.println(cls);

  // ==》System.out.println(cls.toString());

  // 将第二維為5加進去,此時arrayObj為一個一維數組,第一維為3,第二維為5所在的一個數組

  arrayObj = Array.get(arrayObj, 5);

  // 将一維數組的第十個元素設定為37

  Array.setInt(arrayObj, 10, 37);

  int arrayCast[][][] = (int[][][]) array;

  System.out.println(arrayCast[3][5][10]);

衆所周知Java有個Object class,是所有Java classes的繼承根源,其内聲明了數個應該在所有Java class中被改寫的methods:hashCode()、equals()、clone()、toString()、getClass()等。其中getClass()傳回一個Class object。

Class class十分特殊。它和一般classes一樣繼承自Object,其實體用以表達Java程式運作時的classes和interfaces,也用來表達enum、array、primitive Java types

(boolean, byte, char, short, int, long, float, double)以及關鍵詞void。當一個class被加載,或當加載器(class loader)的defineClass()被JVM調用,JVM 便自動産生一個Class object。如果您想借由“修改Java标準庫源碼”來觀察Class object的實際生成時機(例如在Class的constructor内添加一個println()),不能夠!因為Class并沒有public constructor

Class是Reflection起源。針對任何您想探勘的class,唯有先為它産生一個Class object,接下來才能經由後者喚起為數十多個的Reflection APIs

Java允許我們從多種途徑為一個class生成對應的Class object

Class object 誕生管道 

示例 

 運用getClass()

注:每個class都有此函數運用

 String str="abc";

Class c1=str.getClass();//str所對應的Class類

 運用Class.getSuperClass()

 Button b=new Button;

Class c1=b.getClass();

Class c2=c1.getSuperClass();//c1的父類所對應的Class類

 運用static method Class.forname()

(最常被使用)

 Class c1=Class.forName("java.lang.String");//java.lang.String所對應的Class類

Class c2=Class.forName("java.awt.Button");

Class c3=Class.forName("java.util.LinkedList$Entry");

Class c4=Class.forName("I");

Class c5=Class.forName("[I");

 運用.class文法

 Class c1=String.class;//String所對應的Class類

Class c2=java.awt.Button.class;

Class c3=Main.InnerClass.class;

Class c4=int.class;

Class c5=int[].class;

 運用primitive wrapper classes的TYPE的文法

 Class c1=Boolean.TYPE;//Boolean所對應的Class類

Class c2=Byte.TYPE;

Class c3=Character.TYPE;

Class c4=Short.TYPE;

Class c5=Integer.TYPE;

Class c6=Long.TYPE;

Class c7=Float.TYPE;

Class c8=Double.TYPE;

Class c9=Void.TYPE;

欲生成對象實體,在Reflection 動态機制中有兩種作法,一個針對“無自變量ctor”,一個針對“帶參數ctor”。如果欲調用的是“帶參數ctor“就比較麻煩些,不再調用Class的newInstance(),而是調用Constructor 的newInstance()。首先準備一個Class[]做為ctor的參數類型(本例指定

為一個double和一個int),然後以此為自變量調用getConstructor(),獲得一個專屬ctor。接下來再準備一個Object[] 做為ctor實參值(本例指定3.14159和125),調用上述專屬ctor的newInstance()。

Class c=Class.forName("DynTest");

Object obj=null;

obj=c.newInstance();//不帶自變量

System.out.println(obj);

動态生成“Class object 所對應之class”的對象實體;無自變量。

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

Constructor ctor=c.getConstructor(pTypes);

//指定parameter list,便可獲得特定之ctor

Object[] arg=new Object[]{3.14159,125};//自變量

obj=ctor.newInstance(arg);

動态生成“Class Object對應之Class“的實體對象;自變量以Object[]表示

這個動作和上述調用“帶參數之ctor”相當類似。

首先準備一個Class[]做為參數類型(本例指定其中一個是String,另一個是Hashtable),

然後以此為自變量調用getMethod(),獲得特定的Method object。

接下來準備一個Object[]放置自變量,然後調用上述所得之特定Method object的invoke()。

為什麼獲得Method object時不需指定回返類型?

因為method overloading機制要求signature必須唯一,而回返類型并非signature的一個成份。

換句話說,隻要指定了method名稱和參數列,就一定指出了一個獨一無二的method。

public String func(String s,Hashtable ht){

 ...

 System.out.printl("func invoked");

 return s;

public static void main(String args[]){

 Class c=Class.forName("Test");

 Class ptypes[]=new Class[2];

 ptypes[0]=Class.forName("java.lang.String");

 ptypes[1]=Class.forName("java.util.Hashtable");

 method m=c.getMethod("func",ptypes);

 Test obj=new Test();

 Object args[]=new Object[2];

 arg[0]=new String("Hello World");

 arg[1]=null;

 Object r=m.invoke(obj,arg);

 Integer rval=(String)r;

 System.out.println(rval);

與先前兩個動作相比,“變更field内容”輕松多了,因為它不需要參數和自變量。

首先調用Class的getField()并指定field名稱。

獲得特定的Field object之後便可直接調用Field的get()和set()

public class Test{

 public double d;

 public static void main(String args[]){

  Class c=Class.forName("Test");

  Field f=c.getField("d");//指定field名稱

  Test obj=new Test();

  System.out.println("d="+(Double)f.get(obj));

  f.set(obj,12.34);

  System.out.println("d="+obj.d);

動态變更field内容。