Java反射 - 本文檔來着Oracle 官方

使用Java反射

由格倫麥克拉斯基

1998年1月

反射是Java編程語言的一個特性。它允許執行的Java程序檢查或“內省”自身,並操縱程序的內部屬性。例如,Java類可以獲取其所有成員的名稱並顯示它們。

從內部檢查和操作Java類的能力聽起來可能不是很多,但在其他編程語言中,此功能根本不存在。例如,Pascal,C或C ++程序中無法獲取有關該程序中定義的函數的信息。

在JavaBeans中可以有效地使用反射,其中可以通過構建器工具直觀地操作軟件組件。該工具使用反射來獲取動態加載的Java組件(類)的屬性。

一個簡單的例子

要了解反射是如何工作的,請考慮以下簡單示例:

   import java.lang.reflect.*;
 
   public class DumpMethods {
      public static void main(String args[])
      {
         try {
            Class c = Class.forName(args[0]);
            Method m[] = c.getDeclaredMethods();
            for (int i = 0; i < m.length; i++)
            System.out.println(m[i].toString());
         }
         catch (Throwable e) {
            System.err.println(e);
         }
      }
   }

對於調用:

  java DumpMethods java.util.Stack 

輸出是:

   public java.lang.Object java.util.Stack.push( java.lang.Object)
   public synchronized     java.lang.Object java.util.Stack.pop()
   public synchronized     java.lang.Object java.util.Stack.peek()
   public boolean java.util.Stack.empty()
   public synchronized     int java.util.Stack.search(java.lang.Object)

也就是說,java.util.Stack列出了類的方法名稱,以及它們的完全限定參數和返回類型。

此程序使用class.forName,加載指定的類,然後調用getDeclaredMethods以檢索類中定義的方法列表。java.lang.reflect.Method是一個表示單個類方法的類。

設置使用反射

反射類,例如Method,在java.lang.reflect中找到。使用這些類必須遵循三個步驟。第一步是獲取java.lang.Class要操作的類的對象。java.lang.Class用於表示正在運行的Java程序中的類和接口。

獲取Class對象的一種方法是:

 Class c = Class.forName("java.lang.String"); 

獲取Class對象String。另一種方法是使用:

   class c = int.class; 

要麼

  class c = Integer.TYPE; 

獲取基本類型的類信息。後一種方法訪問基礎類型TYPE的包裝器的預定義字段(例如Integer)。

第二步是調用一個方法,例如getDeclaredMethods,獲取該類聲明的所有方法的列表。

一旦掌握了這些信息,第三步就是使用反射API來操縱信息。例如,序列:

  Class c = Class.forName("java.lang.String");   
  Method m[] = c.getDeclaredMethods();   
  System.out.println(m[0].toString());

將顯示聲明的第一個方法的文本表示String

在下面的示例中,這三個步驟相結合,以展示如何使用反射處理特定應用程序的自包含插圖。

模擬instanceof運算符

一旦掌握了類信息,通常下一步就是詢問有關Class對象的基本問題。例如,該Class.isInstance方法可用於模擬instanceof運算符:

class A {}

   public class instance1 {
      public static void main(String args[])
      {
         try {
            Class cls = Class.forName("A");
            boolean b1 
              = cls.isInstance(new Integer(37));
            System.out.println(b1);
            boolean b2 = cls.isInstance(new A());
            System.out.println(b2);
         }
         catch (Throwable e) {
            System.err.println(e);
         }
      }
   }

在此示例中,A創建了一個Class對象,然後檢查類實例對象以查看它們是否是實例AInteger(37)不是,但是new A()

找出關於類的方法

反射最有價值和最基本的用途之一是找出在類中定義的方法。爲此,可以使用以下代碼:

import java.lang.reflect.*;

   public class method1 {
      private int f1(
       Object p, int x) throws NullPointerException
      {
         if (p == null)
            throw new NullPointerException();
         return x;
      }
        
      public static void main(String args[])
      {
         try {
           Class cls = Class.forName("method1");
        
            Method methlist[] 
              = cls.getDeclaredMethods();
            for (int i = 0; i < methlist.length;
               i++) {  
               Method m = methlist[i];
               System.out.println("name 
                 = " + m.getName());
               System.out.println("decl class = " +
                              m.getDeclaringClass());
               Class pvec[] = m.getParameterTypes();
               for (int j = 0; j < pvec.length; j++)
                  System.out.println("
                   param #" + j + " " + pvec[j]);
               Class evec[] = m.getExceptionTypes();
               for (int j = 0; j < evec.length; j++)
                  System.out.println("exc #" + j 
                    + " " + evec[j]);
               System.out.println("return type = " +
                                  m.getReturnType());
               System.out.println("-----");
            }
         }
         catch (Throwable e) {
            System.err.println(e);
         }
      }
   }


程序首先獲取method1的類描述,然後調用getDeclaredMethods以檢索Method對象列表,對應於類中定義的每個方法。這些包括public,protected,package和private方法。如果您getMethods在程序中使用而不是getDeclaredMethods,則還可以獲取繼承方法的信息。

Method獲得對象列表後,只需顯示有關每種方法的參數類型,異常類型和返回類型的信息。這些類型中的每一種,無論它們是基本類型還是類類型,都由類描述符表示。

該計劃的輸出是:

   name = f1
   decl class = class method1
   param #0 class java.lang.Object
   param #1 int
   exc #0 class java.lang.NullPointerException
   return type = int
   -----
   name = main
   decl class = class method1
   param #0 class [Ljava.lang.String;
   return type = void
   -----

 

獲取有關構造函數的信息

類似的方法用於瞭解類的構造函數。例如:

import java.lang.reflect.*;
        
   public class constructor1 {
      public constructor1()
      {
      }
        
      protected constructor1(int i, double d)
      {
      }
        
      public static void main(String args[])
      {
         try {
           Class cls = Class.forName("constructor1");
        
           Constructor ctorlist[]
               = cls.getDeclaredConstructors();
         for (int i = 0; i < ctorlist.length; i++) {
               Constructor ct = ctorlist[i];
               System.out.println("name 
                 = " + ct.getName());
               System.out.println("decl class = " +
                            ct.getDeclaringClass());
               Class pvec[] = ct.getParameterTypes();
               for (int j = 0; j < pvec.length; j++)
                  System.out.println("param #" 
                     + j + " " + pvec[j]);
               Class evec[] = ct.getExceptionTypes();
               for (int j = 0; j < evec.length; j++)
                  System.out.println(
                    "exc #" + j + " " + evec[j]);
               System.out.println("-----");
            }
          }
          catch (Throwable e) {
             System.err.println(e);
          }
      }
   }


在此示例中沒有檢索到返回類型信息,因爲構造函數實際上沒有真正的返回類型。

運行此程序時,輸出爲:

   name = constructor1
   decl class = class constructor1
   -----
   name = constructor1
   decl class = class constructor1
   param#0 int
   param#1 double
   -----

 

瞭解類字段

還可以找出在類中定義的數據字段。爲此,可以使用以下代碼:

import java.lang.reflect.*;
        
   public class field1 {
      private double d;
      public static final int i = 37;
      String s = "testing";
        
      public static void main(String args[])
      {
         try {
            Class cls = Class.forName("field1");
        
            Field fieldlist[] 
              = cls.getDeclaredFields();
            for (int i 
              = 0; i < fieldlist.length; i++) {
               Field fld = fieldlist[i];
               System.out.println("name
                  = " + fld.getName());
               System.out.println("decl class = " +
                           fld.getDeclaringClass());
               System.out.println("type
                  = " + fld.getType());
               int mod = fld.getModifiers();
               System.out.println("modifiers = " +
                          Modifier.toString(mod));
               System.out.println("-----");
            }
          }
          catch (Throwable e) {
             System.err.println(e);
          }
       }
   }


此示例與之前的示例類似。一個新功能是使用Modifier。這是一個反射類,表示在字段成員上找到的修飾符,例如“ private int”。修飾符本身由整數表示,Modifier.toString用於以“官方”聲明順序(例如“ static”之前的“ final”)返回字符串表示。該計劃的輸出是:

   name = d
   decl class = class field1
   type = double
   modifiers = private
   -----
   name = i
   decl class = class field1
   type = int
   modifiers = public static final
   -----
   name = s
   decl class = class field1
   type = class java.lang.String
   modifiers =
   ----- 


與方法一樣,可以獲取有關在class(getDeclaredFields)中聲明的字段的信息,或者獲取有關在超類(getFields)中定義的字段的信息。

按名稱調用方法

到目前爲止,已經呈現的示例都與獲取類信息有關。但是也可以以其他方式使用反射,例如調用指定名稱的方法。

要了解其工作原理,請考慮以下示例:

import java.lang.reflect.*;
        
   public class method2 {
      public int add(int a, int b)
      {
         return a + b;
      }
        
      public static void main(String args[])
      {
         try {
           Class cls = Class.forName("method2");
           Class partypes[] = new Class[2];
            partypes[0] = Integer.TYPE;
            partypes[1] = Integer.TYPE;
            Method meth = cls.getMethod(
              "add", partypes);
            method2 methobj = new method2();
            Object arglist[] = new Object[2];
            arglist[0] = new Integer(37);
            arglist[1] = new Integer(47);
            Object retobj 
              = meth.invoke(methobj, arglist);
            Integer retval = (Integer)retobj;
            System.out.println(retval.intValue());
         }
         catch (Throwable e) {
            System.err.println(e);
         }
      }
   }


假設一個程序想要調用該add方法,但直到執行時才知道這個。也就是說,在執行期間指定方法的名稱(例如,這可能由JavaBeans開發環境完成)。上述程序顯示了這樣做的一種方法。

getMethod用於在類中查找具有兩個整數參數類型且具有適當名稱的方法。一旦找到此方法並將其捕獲到Method對象中,就會在適當類型的對象實例上調用它。要調用方法,必須構造參數列表,基本整數值37和47包含在Integer對象中。返回值(84)也包裝在一個Integer對象中。

創建新對象

沒有與構造函數的方法調用等效,因爲調用構造函數等同於創建新對象(最精確的是,創建新對象涉及內存分配和對象構造)。所以與上一個例子最接近的等價是:

 import java.lang.reflect.*;
        
   public class constructor2 {
      public constructor2()
      {
      }
        
      public constructor2(int a, int b)
      {
         System.out.println(
           "a = " + a + " b = " + b);
      }
        
      public static void main(String args[])
      {
         try {
           Class cls = Class.forName("constructor2");
           Class partypes[] = new Class[2];
            partypes[0] = Integer.TYPE;
            partypes[1] = Integer.TYPE;
            Constructor ct 
              = cls.getConstructor(partypes);
            Object arglist[] = new Object[2];
            arglist[0] = new Integer(37);
            arglist[1] = new Integer(47);
            Object retobj = ct.newInstance(arglist);
         }
         catch (Throwable e) {
            System.err.println(e);
         }
      }
   } 


它找到一個處理指定參數類型並調用它的構造函數,以創建該對象的新實例。這種方法的價值在於它純粹是動態的,在執行時使用構造函數查找和調用,而不是在編譯時。

改變字段的值

反射的另一個用途是更改對象中數據字段的值。它的值再次來自反射的動態特性,其中可以在執行程序中按名稱查找字段,然後更改其值。以下示例說明了這一點:

import java.lang.reflect.*;
        
   public class field2 {
      public double d;
        
      public static void main(String args[])
      {
         try {
            Class cls = Class.forName("field2");
            Field fld = cls.getField("d");
            field2 f2obj = new field2();
            System.out.println("d = " + f2obj.d);
            fld.setDouble(f2obj, 12.34);
            System.out.println("d = " + f2obj.d);
         }
         catch (Throwable e) {
            System.err.println(e);
         }
      }
   } 


在此示例中,d字段的值設置爲12.34。

使用數組

反射的最後一個用途是創建和操作數組。Java語言中的數組是一種特殊類型的類,可以將數組引用賦給Object引用。

要查看數組的工作方式,請考慮以下示例:

import java.lang.reflect.*;
        
   public class array1 {
      public static void main(String args[])
      {
         try {
            Class cls = Class.forName(
              "java.lang.String");
            Object arr = Array.newInstance(cls, 10);
            Array.set(arr, 5, "this is a test");
            String s = (String)Array.get(arr, 5);
            System.out.println(s);
         }
         catch (Throwable e) {
            System.err.println(e);
         }
      }
   }


此示例創建一個10長的字符串數組,然後將數組中的位置5設置爲字符串值。檢索並顯示該值。

以下代碼說明了更復雜的數組操作:

import java.lang.reflect.*;
        
   public class array2 {
      public static void main(String args[])
      {
         int dims[] = new int[]{5, 10, 15};
         Object arr 
           = Array.newInstance(Integer.TYPE, dims);
        
         Object arrobj = Array.get(arr, 3);
         Class cls = 
           arrobj.getClass().getComponentType();
         System.out.println(cls);
         arrobj = Array.get(arrobj, 5);
         Array.setInt(arrobj, 10, 37);
        
         int arrcast[][][] = (int[][][])arr;
         System.out.println(arrcast[3][5][10]);
      }
   }


此示例創建一個5 x 10 x 15的整數數組,然後繼續將數組中的位置[3] [5] [10]設置爲值37.請注意,多維數組實際上是一個數組數組因此,例如,在第一個Array.get之後,arrobj中的結果是10 x 15陣列。再次將其剝離以獲得15個長的陣列,並使用該陣列中的第10個插槽進行設置Array.setInt

請注意,創建的數組類型是動態的,不必在編譯時知道。

摘要

Java反射非常有用,因爲它支持按名稱動態檢索有關類和數據結構的信息,並允許在執行的Java程序中對它們進行操作。此功能非常強大,並且在其他傳統語言(如C,C ++,Fortran或Pascal)中沒有等效功能。

Glen McCluskey自1988年以來一直專注於編程語言。他參與Java和C ++性能,測試和技術文檔領域的諮詢。

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