課程:操縱對象

軟件開發工具,比如圖形用戶界面構建器合調試器,需要操縱對象在運行時。例如,一個圖形用戶界面構建器可以允許最終用戶選擇一個Button匆一個組件菜單,創建一個Button對象,然後點擊Button在運行應用程序時。如果你創建軟件開發工具,你將要發現反射API特性的優勢。

這一節課有如下的階段:

創建對象

如何創建一個對象,如果你不知道類名直到運行時?你將發現答案。

創建對象的最簡單方式是使用new操作符:

Rectangle r = new Rectangle();

在應用中這些技術已經足够用了,因爲大多數情況下你在編譯時已經知道了對象的類名。然而,如果你正在編寫開發工具,你業務不知道對象的類名知道運行時。例如,一個圖形用戶界面構建器允許用戶拖拽各種圖形用戶界面組件到設計頁面。這種情況下,你可能需要按如下的順序創建圖形用戶界面:

String className; 
 
// . . . 從用戶界面加載類名
Object o = new (className); // 錯誤

前面的情况是無效的因爲new操作符不能接受參數。幸運的是,借助反射API你可以創建一個對象,這個對象的類知道運行時才被知道。你調用的方法麳創建一個對象依賴域構造函數是否有參數。這一節討論這些主題:

使用沒有參數的搆造函數

如果你需要創建一個沒有參數的構造函數的對象,你可以調用Class對象的newInstance方法。newInstance方法拋出一個NoSuchMethodException,如果這個類的構造函數沒有參數。有關Constructor對象的更多信息,請參見發現類構造函數這一節。

以下的示例程序創建Rectangle類的實例,通過使用沒有參數的構造方法。

import java.lang.reflect.*;
import java.awt.*;
 
class SampleNoArg {
 
   public static void main(String[] args) {
      Rectangle r = (Rectangle) createObject("java.awt.Rectangle");
      System.out.println(r.toString());
   }
 
   static Object createObject(String className) {
      Object object = null;
      try {
          Class classDefinition = Class.forName(className);
          object = classDefinition.newInstance();
      } catch (InstantiationException e) {
          System.out.println(e);
      } catch (IllegalAccessException e) {
          System.out.println(e);
      } catch (ClassNotFoundException e) {
          System.out.println(e);
      }
      return object;
   }
}

輸出:

java.awt.Rectangle[x=0,y=0,width=0,height=0]

使用有參數的構造函數

爲了使用有參數的搆造函數創建對象,你可以調用Constructor對象的newInstance方法,不是Class對象。這個技術包含以下步驟:

1.  創建一個Class對象。

2.  創建一個Constructor對象通過調用Class對象的getConstructorgetConstructor方法有一個參數:一個Class對象的數組,這些是相應的構造函數的參數。

3.  創建一個對象通過調用Constructor對象的newInstance方法。newInstance方法有一個參數:一個對象數組,這個對象數組的元素是傳入構造函數的參數值。

示例程序創建一個Rectangle通過搆造函數接受兩個整型參數。

Rectangle rectangle = new Rectangle(12, 34);
這個搆造函數的參數是原始類型,但是這個參數值傳給newInstance必須爲對象。所以,每一個原始類型被封裝成Integer對象。
示例程序硬編碼通過傳給getConstructor方法。在現實生活的應用程序中你可能讓用戶選擇搆造函數。爲了驗證用戶的選擇,你可以使用方法,這些會在發現類的構造函數中被講解。
源代碼如下:
import java.lang.reflect.*;
import java.awt.*;
 
class SampleInstance {
 
   public static void main(String[] args) {
 
      Rectangle rectangle;
      Class rectangleDefinition;
      Class[] intArgsClass = new Class[] {int.class, int.class};
      Integer height = new Integer(12);
      Integer width = new Integer(34);
      Object[] intArgs = new Object[] {height, width};
      Constructor intArgsConstructor;
 
      try {
        rectangleDefinition = Class.forName("java.awt.Rectangle");
        intArgsConstructor = 
            rectangleDefinition.getConstructor(intArgsClass);
        rectangle = 
            (Rectangle) createObject(intArgsConstructor, intArgs);
      } catch (ClassNotFoundException e) {
          System.out.println(e);
      } catch (NoSuchMethodException e) {
          System.out.println(e);
      }
   }
 
   public static Object createObject(Constructor constructor, 
                                     Object[] arguments) {
 
      System.out.println ("Constructor: " + constructor.toString());
      Object object = null;
 
      try {
        object = constructor.newInstance(arguments);
        System.out.println ("Object: " + object.toString());
        return object;
      } catch (InstantiationException e) {
          System.out.println(e);
      } catch (IllegalAccessException e) {
          System.out.println(e);
      } catch (IllegalArgumentException e) {
          System.out.println(e);
      } catch (InvocationTargetException e) {
          System.out.println(e);
      }
      return object;
   }
}
輸出如下:
Constructor: public java.awt.Rectangle(int,int)
Object: java.awt.Rectangle[x=0,y=0,width=12,height=34]
 

獲得域的值

在這一節你將要學到如何獲得對象的域的值,即使你不知道對象的名字,直到運行時。

如果你正在編寫一個開發工具例如調試器,你必須獲得域的值。需要三步:

1.  創建一個Class對象。返回類對象那一節展示了如何麳做。

2.  創建一個Field對象通過getField方法。瞭解更多信息,參見指定類的域那一節。

3.  調用Field對象的get方法的其中一個。

Field類有特定的方法麳獲得原始類型的值。例如,getInt方法返回一個int值的內容,getFloat返回一個浮點數,等等。如果域存儲一個對象,使用get方法麳返回一個對象。

以下的示例程序展示了以上三步。這個程序獲得height域的值。因爲height是一個原是類型,get方法返回一個包裝對象。

在示例程序中,height域的名字在編譯時候就知道。然而在開發工具例如一個圖形用戶接口構建器,域名字直到運行時才知道。爲了發現屬于類的域,你可以使用在指定類的域那一節被描述。

以下是源代碼:

import java.lang.reflect.*;
import java.awt.*;
 
class SampleGet {
 
   public static void main(String[] args) {
      Rectangle r = new Rectangle(100, 325);
      printHeight(r);
 
   }
 
   static void printHeight(Rectangle r) {
      Field heightField;
      Integer heightValue;
      Class c = r.getClass();
      try {
        heightField = c.getField("height");
        heightValue = (Integer) heightField.get(r);
        System.out.println("Height: " + heightValue.toString());
      } catch (NoSuchFieldException e) {
          System.out.println(e);
      } catch (SecurityException e) {
          System.out.println(e);
      } catch (IllegalAccessException e) {
          System.out.println(e);
      }
   }
}

輸出:

Height: 325

設置域的值

在運行時不僅可以獲得域的值,還可以設置域的值。

一些調試器允許用戶改變域的值。如果你正在編寫的工具具備這個特性,你必須調用Field類的set方法。爲了修改一個域的值,執行以下步驟:

1.  創建一個Class對象。更多信息,可以參見返回Class對象。

2.  創建一個通過調用getField方法獲得Field對象。參見指定Class域那一節。

3.  調用適當的Field對象的set方法。

Field類提供幾個set方法。特定的方法,例如setBooleansetInt,這些方法是爲了修改原始類型。如果你想要修改的域是一個對象,則要調用set方法。你可以調用set麳修改一個原始類型,但是你必須使用適當的包裝對象。

示例程序修改width域。因爲width是一個原始類型,int,傳遞給set的是一個Integer,是一個包裝類對象。

import java.lang.reflect.*;
import java.awt.*;
 
class SampleSet {
 
   public static void main(String[] args) {
      Rectangle r = new Rectangle(100, 20);
      System.out.println("original: " + r.toString());
      modifyWidth(r, new Integer(300));
      System.out.println("modified: " + r.toString());
   }
 
   static void modifyWidth(Rectangle r, Integer widthParam ) {
      Field widthField;
      Integer widthValue;
      Class c = r.getClass();
      try {
        widthField = c.getField("width");
        widthField.set(r, widthParam);
      } catch (NoSuchFieldException e) {
          System.out.println(e);
      } catch (IllegalAccessException e) {
          System.out.println(e);
      }
   }
}

輸出:

original: java.awt.Rectangle[x=0,y=0,width=100,height=20]
modified: java.awt.Rectangle[x=0,y=0,width=300,height=20]

調用方法

這一節你將要看到如何動態的調用方法。給你一個對象,你可以發現它的類所定義的方法,然後調用這些方法。

假設你正在編寫一個調試器,這個調試器允許用戶在調試期間選擇幷且調用方法。因爲你在編譯時不知道哪個方法將是用戶所調用的,你不能硬編碼這個方法名字在你的源代碼中。你必須按照如下步驟:

1.  創建一個Class對象對應你想要調用方法的對象。詳見返回Class對象。

2.  創建一個Method對象,通過調用getMethod方法。getMethod方法有兩個參數:一個包含方法名稱的字符串,還有一個是Class對象的數組。數組中的每一個元素對應你要調用方法的一個參數。要瞭解返回Method對象的更多信息,參見獲得Method信息這一節。

3.  通過調用invoke調用犯法。Invoke方法有兩個參數:一個參數值的數組和一個對象。

示例程序展示的是如何動態調用方法。程序返回Method對象,這個對象對應的是String.concat方法,然後使用invoke麳鏈接兩個字符串對象。

import java.lang.reflect.*;
 
class SampleInvoke {
 
   public static void main(String[] args) {
      String firstWord = "Hello ";
      String secondWord = "everybody.";
      String bothWords = append(firstWord, secondWord);
      System.out.println(bothWords);
   }
 
   public static String append(String firstWord, String secondWord) {
      String result = null;
      Class c = String.class;
      Class[] parameterTypes = new Class[] {String.class};
      Method concatMethod;
      Object[] arguments = new Object[] {secondWord};
      try {
        concatMethod = c.getMethod("concat", parameterTypes);
        result = (String) concatMethod.invoke(firstWord, arguments);//第一個是對象,第二個是值。
      } catch (NoSuchMethodException e) {
          System.out.println(e);
      } catch (IllegalAccessException e) {
          System.out.println(e);
      } catch (InvocationTargetException e) {
          System.out.println(e);
      }
      return result;
   }
}

輸出:

Hello everybody.

 

posted on 2007-02-20 14:53  IT Person  阅读(391)  评论(0编辑  收藏  举报