首頁 > 軟體

Java中反射的學習筆記分享

2022-11-07 14:00:03

簡介

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

從程式內部檢查和操作Java類的能力聽起來可能不太顯示,但是在其他程式語言中,這個特性根本不存在。例如,在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.反射中找到。使用這些類必須遵循三個步驟。第一步是獲得一個java.lang.Class要操作的類的物件。java.lang.Class用於表示正在執行的Java程式中的類和介面。

獲取類物件的一種方法是:

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

上述程式碼獲取的類物件 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());

在下面的範例中,將三個步驟結合在一起,以呈現如何使用反射處理特定應用的獨立插圖。

模擬instanceof運算

一旦掌握了類資訊,下一步通常是詢問有關類物件的基本問題。

例如,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被建立,然後我們檢查類範例物件,以檢視它們是否是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、protect、package和priva。如果你在程式中使用getMethods 而不是getDeclaredMethods,還可以獲取繼承方法的資訊。

程式的輸出為:

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用於返回預設宣告順序中的字串表示形式 (例如final之前的static)。程式的輸出為:

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 =
   -----

與方法一樣,可以僅獲取有關類中宣告的欄位的資訊 (getDeclaredFields),或獲取有關超類中定義的欄位的資訊 (getFields)。

按名稱呼叫方法

到目前為止,已經提出的例子都與獲取class有關。但是也可以以其他方式使用反射,例如呼叫指定名稱的方法。

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

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用於在類中查詢具有兩個integer引數型別並具有適當名稱的方法。一旦找到此方法並將其捕獲到Method 物件,它是在適當型別的物件範例上呼叫的。要呼叫方法,必須構造一個參數列,基本整數值為37和47Integer 物件。返回值 (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]);
      }
   }

此範例建立一個5x10x15的int陣列,然後繼續將陣列中的位置 [3][5][10] 設定為值37。請注意,多維陣列實際上是陣列陣列,因此,例如,在第一個array.get之後,arrobj中的結果是10x15陣列。再次將其剝離以獲得15長的陣列,並使用Array.setInt

請注意,建立的陣列型別是動態的,不必在編譯時知道。

總結

Java反射非常有用,因為它支援按名稱動態檢索有關類和資料結構的資訊,並允許在執行的Java程式中進行操作。此功能非常強大,但是也要謹慎使用

以上就是Java中反射的學習筆記分享的詳細內容,更多關於Java反射的資料請關注it145.com其它相關文章!


IT145.com E-mail:sddin#qq.com