程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> Classworking 工具箱: 反射泛型

Classworking 工具箱: 反射泛型

編輯:關於JAVA

Java" 5 擴展了 Java 語言類型系統以支持類、方法和值的參數化類型。參數化的類型通過確保使用正確的類型及消除從源代碼進行類型轉換提供了重要的編譯時好處。除了這些編譯時好處,類型信息對於 classworking 工具操縱 Java 代碼也有幫助。在本文中,JiBX 首席開發員 Dennis Sosnoski 分析了如何用反射深入參數化類型的內部,並充分展示了 Java 5 應用程序數據結構的優勢。

  許多工具都是圍繞使用 Java 反射而設計的,它們的用途包括從用數據值填充 GUI 組件到在運行的應用程序中動態裝載新功能。反射對於在運行時分析數據結構特別有用,許多在內部對象結構與外部格式(包括 XML、數據庫和其他持久化格式)之間轉換的框架都基於對數據結構的反射分析。

  使用反射分析數據結構的一個問題是標准 Java 集合類(如 java.util.ArrayList)對於反射來說總是“死胡同(dead-end)” —— 到達一個集合類後,無法再訪問數據結構的更多細節,因為沒有關於集合中包含的項目類型的信息。Java 5 改變了這一情況,它增加了對泛型的支持,將所有集合類轉換為支持類型的泛型形式。Java 5 還擴展了反射 API ,支持在運行時對泛型類型信息進行訪問。這些改變使反射可以比以往更深入地挖掘數據結構。

包裝之下代碼

  許多文章討論了 Java 5 的泛型功能的使用。對於本文,假定您已經了解泛型的基本知識。我們首先使用一些示例代碼,然後直接討論如何在運行時訪問泛型信息。

  作為使用泛型的一個例子,我准備使用一個表示一組路徑中的目錄和文件的數據結構。清單 1 給出了這個數據結構根類的代碼。PathDirectory 類取路徑 String 數組作為構造函數參數。這個構造函數將每一個字符串解釋為目錄路徑,並構造一個數據結構以表示這個路徑下面的文件和子目錄。處理每一路徑時,這個構造函數就將這個路徑和這個路徑的數據結構加到一個成對集合(pair collection)中。

清單 1. 目錄信息集

public class PathDirectory implements Iterable

{

private final PairCollection m_pathPairs;

public PathDirectory(String[] paths) {

m_pathPairs = new PairCollection();

for (String path : paths) {

File file = new File(path);

if (file.exists() && file.isDirectory()) {

DirInfo info = new DirInfo(new File(path));

m_pathPairs.add(path, info);

}

}

}

public PairCollection.PairIterator iterator() {

return m_pathPairs.iterator();

}

public static void main(String[] args) {

PathDirectory inst = new PathDirectory(args);

PairCollection.PairIterator iter = inst.iterator();

while (iter.hasNext()) {

String path = iter.next();

DirInfo info = iter.matching();

System.out.println("Directory " + path + " has " +

info.getFiles().size() + " files and " +

info.getDirectories().size() + " child directorIEs");

}

}

}

  清單 2 給出了 PairCollection 的代碼。這個泛型類處理成對的值,類型參數給出了這些對中項目的類型。它提供了一個 add() 方法向集合中加入一個元組(tuple),一個 clear() 方法清空集合中所有元組,一個 iterator() 方法返回遍歷集合中所有對的迭代器。內部 PairIterator 類實現由後一個方法返回的特殊迭代器,它定義了一個額外的 matching() 方法,這個方法用於得到由標准 next() 方法返回的值的配對(第二個)值。

清單 2. 泛型對集合

public class PairCollection implements Iterable

{

// code assumes random Access so force implementation class

private final ArrayList m_tValues;

private final ArrayList m_uValues;

public PairCollection() {

m_tValues = new ArrayList();

m_uValues = new ArrayList();

}

public void add(T t, U u) {

m_tValues.add(t);

m_uValues.add(u);

}

public void clear() {

m_tValues.clear();

m_uValues.clear();

}

public PairIterator iterator() {

return new PairIterator();

}

public class PairIterator implements Iterator

{

private int m_offset;

public boolean hasNext() {

return m_offset < m_tValues.size();

}

public T next() {

if (m_offset < m_tValues.size()) {

return m_tValues.get(m_offset++);

} else {

throw new NoSuchElementException();

}

}

public U matching() {

if (m_offset > 0) {

return m_uValues.get(m_offset-1);

} else {

throw new NoSuchElementException();

}

}

public void remove() {

throw new UnsupportedOperationException();

}

}

}

  PairCollection 對於包含值的實際集合在內部使用泛型。它用第一個參數類型實現了 Java.lang.Iterable 接口,從而可以直接在新型 for 循環中使用以遍歷每對中的第一個值。不幸的是,在使用新型 for 循環時,無法訪問實際的迭代器,因而無法獲取每一對的第二個值。這就是為什麼 清單 1 中的 main() 測試方法使用 while 循環而不是一個新的 for 循環。

  清單 3 給出了包含目錄和文件信息的一對類的代碼。DirInfo 類使用有類型的 Java.util.List 集合表示普通文件和目錄的子目錄。構造函數將這些集合創建為不可修改的列表,使得它們可以安全地直接返回。FileInfo 類更簡單,只包含文件名和最後修改日期。

清單 3. 目錄和文件數據類

public class DirInfo

{

private final List m_files;

private final List m_directorIEs;

private final Date m_lastModify;

public DirInfo(File dir) {

m_lastModify = new Date(dir.lastModifIEd());

File[] childs = dir.listFiles();

List files = new ArrayList();

List dirs = new ArrayList();

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

File child = childs[i];

if (child.isDirectory()) {

dirs.add(new DirInfo(child));

} else if (child.isFile()) {

files.add(new FileInfo(child));

}

}

m_files = Collections.unmodifiableList(files);

m_directorIEs = Collections.unmodifiableList(dirs);

}

public List getDirectorIEs() {

return m_directorIEs;

}

public List getFiles() {

return m_files;

}

public Date getLastModify() {

return m_lastModify;

}

}

public class FileInfo

{

private final String m_name;

private final Date m_lastModify;

public FileInfo(File file) {

m_name = file.getName();

m_lastModify = new Date(file.lastModifIEd());

}

public Date getLastModify() {

return m_lastModify;

}

public String getName() {

return m_name;

}

}

  清單 4 給出了 清單 1 中的 main() 方法的運行示例:

清單 4. 示例運行

[dennis]$ Java -cp . com.sosnoski.generics.PathDirectory

/home/dennis/bin /home/dennis/xtools /home/dennis/docs/business

Directory /home/dennis/bin has 31 files and 0 child directorIEs

Directory /home/dennis/xtools has 0 files and 3 child directorIEs

Directory /home/dennis/docs/business has 34 files and 34 child directorIEs

泛型反射

  泛型是在 Java 平台上作為編譯時轉換實現的。編譯器實際上生成與使用非泛型源代碼時相同的字節指令,插入運行時類型轉換以在每次訪問時將值轉換為正確的類型。盡管是相同的字節碼,但是類型參數信息用 一個新的簽名(signature) 屬性記錄在類模式中。JVM 在裝載類時記錄這個簽名信息,並在運行時通過反射使它可用。在這一節,我將深入挖掘反射 API 如何使類型信息可用的細節。

類型反射接口

  通過反射訪問類型參數信息有些復雜。首先,需要有一個具有所提供類型信息的字段(或者其他可提供類型的辦法,如方法參數或者返回類型)。然後可以用 Java 5 新增的 getGenericType() 方法從這個字段的 java.lang.reflect.FIEld 實例提取特定於泛型的信息。這個新方法返回一個 Java.lang.reflect.Type 實例。

  惟一的問題是 Type 是一個沒有方法的接口。在實例化後,需要檢查擴展了 Type 的子接口以了解得到的是什麼(以及如何使用它)。Javadocs 列出了四種子接口,我會依次介紹它們。為了方便,我在清單 5 中給出了接口定義。它們都包括在 Java.lang.reflect 包中。

清單 5. Type 子接口

interface GenericArrayType extends Type {

Type getGenericComponentType();

}

interface ParameterizedType extends Type {

Type[] getActualTypeArguments();

Type getOwnerType();

Type getRawType();

}

interface TypeVariable extends Type {

Type[] getBounds();

D getGenericDeclaration();

String getName();

}

interface WildcardType extends Type {

Type[] getLowerBounds();

Type[] getUpperBounds();

}

  Java.lang.reflect.GenericArrayType 是第一個子接口。這個子接口提供了關於數組類型的信息,數組的組件類型可以是參數化的,也可以是一個類型變量。只定義了一個方法 getGenericComponentType(),它返回數組組件 Type。

  Java.lang.reflect.ParameterizedType 是 Type 的第二個子接口。它提供了關於具有特定類型參數的泛型類型的信息。這個接口定義了三個方法,其中最讓人感興趣的(對於本文來說)是 getActualTypeArguments() 方法。這個方法返回一個 (drum role)數組 . . .還有更多的 Type 實例。返回的 Type 表示原來(未參數化的)類型的實際類型參數。

  Type 的第三個子接口是 java.lang.reflect.TypeVariable。這個接口給出了表示一個參數類型的變量(如這個類型名中變量 "D")的細節。這個接口定義了三個方法:getBounds(),它返回(您猜) Type 實例數組;getGenericDeclaration(),它返回對應於類型變量聲明的 Java.lang.reflect.GenericDeclaration 接口的實例;getName(),它返回類型變量在源代碼中使用的名字。這些方法都需要進一步說明。因此我將逐一分析它們。

  由 getBounds() 方法返回的類型數組定義對於變量的類型所施加的限制。這些限制在源代碼中作為在模板變量中以 extends B(其中 “B” 是某種類型)的格式添加的子句進行聲明。很方便, java.lang.reflect.TypeVariable 本身就給出了這種形式的上界定義的一個例子 —— Java.lang.reflect.GenericDeclaration 是類型參數 “D” 的上界,意味著 “D” 必須是擴展或者實現 GenericDeclaration 的類型。

  getGenericDeclaration() 方法提供了一種訪問聲明了 TypeVariable 的 GenericDeclaration 實例的方式。在標准 Java API 中有三個類實現了 GenericDeclaration:java.lang.Class、java.lang.reflect.Constructor 和 Java.lang.reflect.Method。這三個類是有意義的,因為參數類型只能在類、構造函數和方法中聲明。GenericDeclaration 接口定義了一個方法,它返回在聲明中包含的 TypeVariable 的數組。

  getName() 方法只是返回與源代碼中給出的完全一樣的類型變量名。

  Java.lang.reflect.WildcardType 是 Type 的第四個(也是最後一個)子接口。WildcardType 只定義了兩個方法,返回通配類型的下界和上界。在前面,我給出了上界的一個例子,下界也類似,但是它們是通過指定一種類型而定義的,提供的類型必須是它的超接口或者超類。

對一個例子的反射

  我在上一節中描述的反射接口提供了解碼泛型信息的鉤子,但是確定它們有點難度 —— 不管從哪兒開始,每件事都像是循環並回到 Java.lang.reflect.Type。為了展示它們是如何工作的,我將利用 清單 1 代碼中的一個例子並對它進行反射。

  首先,我嘗試訪問 清單 1 的 m_pathPairs 字段的類型信息。清單 6 中的代碼得到這個字段的泛型類型,檢查結果是否為所預期的類型,然後列出原始類型和參數化類型的實際類型參數。在清單 6 的最後以粗體顯示了運行這段代碼的輸出:

清單 6. 第一個反射代碼

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

// get the basic information

Field fIEld =

PathDirectory.class.getDeclaredFIEld("m_pathPairs");

Type gtype = fIEld.getGenericType();

if (gtype instanceof ParameterizedType) {

// list the raw type information

ParameterizedType ptype = (ParameterizedType)gtype;

Type rtype = ptype.getRawType();

System.out.println("rawType is instance of " +

rtype.getClass().getName());

System.out.println(" (" + rtype + ")");

// list the actual type arguments

Type[] targs = ptype.getActualTypeArguments();

System.out.println("actual type arguments are:");

for (int j = 0; j < targs.length; j++) {

System.out.println(" instance of " +

targs[j].getClass().getName() + ":");

System.out.println(" (" + targs[j] + ")");

}

} else {

System.out.println

("getGenericType is not a ParameterizedType!");

}

}

rawType is instance of Java.lang.Class

(class com.sosnoski.generics.PairCollection)

actual type arguments are:

instance of Java.lang.Class:

(class Java.lang.String)

instance of Java.lang.Class:

(class com.sosnoski.generics.DirInfo)

  到目前為止一切都好。m_pathPairs 字段定義為 PairCollection 類型,它匹配反射所訪問的類型信息。不過深入實際的參數化類型定義會有些復雜;返回的 Type 實例是一個沒有實現任何 Type 子接口的 java.lang.Class 對象。幸運的是,Java 5 Class 類本身提供了一個深入泛型類定義的細節的方法。這個方法是 getTypeParameters(),它返回一個 TypeVariable 數組。在清單 7 中,我修改了 清單 6 的代碼以使用這個方法,得到的結果同樣以粗體顯示在代碼中:

清單 7. 深入參數化類型

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

// get the basic information

Field fIEld =

PathDirectory.class.getDeclaredFIEld("m_pathPairs");

ParameterizedType ptype =

(ParameterizedType)fIEld.getGenericType();

Class rclas = (Class)ptype.getRawType();

System.out.println("rawType is class " + rclas.getName());

// list the type variables of the base class

TypeVariable[] tvars = rclas.getTypeParameters();

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

TypeVariable tvar = tvars[i];

System.out.print(" Type variable " +

tvar.getName() + " with upper bounds [");

Type[] btypes = tvar.getBounds();

for (int j = 0; j < btypes.length; j++) {

if (j > 0) {

System.out.print(" ");

}

System.out.print(btypes[j]);

}

System.out.println("]");

}

// list the actual type arguments

Type[] targs = ptype.getActualTypeArguments();

System.out.print("Actual type arguments are\n (");

for (int j = 0; j < targs.length; j++) {

if (j > 0) {

System.out.print(" ");

}

Class tclas = (Class)targs[j];

System.out.print(tclas.getName());

}

System.out.print(")");

}

rawType is class com.sosnoski.generics.PairCollection

Type variable T with upper bounds [class Java.lang.Object]

Type variable U with upper bounds [class Java.lang.Object]

Actual type arguments are

(Java.lang.String com.sosnoski.generics.DirInfo)

  清單 7 的結果顯示了解碼的結構。實際的類型參數可以由泛型類定義的類型變量匹配。在下一節,我將做此工作作為遞推泛型解碼方法的一部分。

泛型遞推

  在上一節,我快速完成了訪問泛型信息的反射方法。現在我將用這些方法構建一個解釋泛型的遞推處理程序。清單 8 給出了相關的代碼:

清單 8. 遞推泛型分析

public class Reflect

{

private static HashSet s_processed = new HashSet();

private static void describe(String lead, Field fIEld) {

// get base and generic types, check kind

Class btype = fIEld.getType();

Type gtype = fIEld.getGenericType();

if (gtype instanceof ParameterizedType) {

// list basic parameterized type information

ParameterizedType ptype = (ParameterizedType)gtype;

System.out.println(lead + fIEld.getName() +

" is of parameterized type");

System.out.println(lead + ' ' + btype.getName());

// print list of actual types for parameters

System.out.print(lead + " using types (");

Type[] actuals = ptype.getActualTypeArguments();

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

if (i > 0) {

System.out.print(" ");

}

Type actual = actuals[i];

if (actual instanceof Class) {

System.out.print(((Class)actual).getName());

} else {

System.out.print(actuals[i]);

}

}

System.out.println(")");

// analyze all parameter type classes

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

Type actual = actuals[i];

if (actual instanceof Class) {

analyze(lead, (Class)actual);

}

}

} else if (gtype instanceof GenericArrayType) {

// list array type and use component type

System.out.println(lead + fIEld.getName() +

" is array type " + gtype);

gtype = ((GenericArrayType)gtype).

getGenericComponentType();

} else {

// just list basic information

System.out.println(lead + fIEld.getName() +

" is of type " + btype.getName());

}

// analyze the base type of this fIEld

analyze(lead, btype);

}

private static void analyze(String lead, Class clas) {

// substitute component type in case of an array

if (clas.isArray()) {

clas = clas.getComponentType();

}

// make sure class should be expanded

String name = clas.getName();

if (!clas.isPrimitive() && !clas.isInterface() &&

!name.startsWith("Java.lang.") &&

!s_processed.contains(name)) {

// print introduction for class

s_processed.add(name);

System.out.println(lead + "Class " +

clas.getName() + " details:");

// process each fIEld of class

String indent = lead + ' ';

Field[] fields = clas.getDeclaredFIElds();

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

Field field = fIElds[i];

if (!Modifier.isStatic(field.getModifIErs())) {

describe(indent, fIEld);

}

}

}

}

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

analyze("", PathDirectory.class);

}

}

  清單 8 中的代碼使用兩個相互遞推的方法進行實際的分析。analyze() 方法取一個類作為參數,通過對每個字段的定義進行必要的處理展開這個類。describe() 方法打印特定字段的類型信息的描述,對在這一過程中它遇到的每一個類調用 analyze()。每個方法還有一個給出當前縮進字符串的參數,它使每一級類嵌套都縮進一些空間。

  清單 9 給出了用 清單 8 中的代碼分析 清單 1、清單 2 和 清單 3 中代碼的完整結構所生成的輸出。

清單 9. 泛型示例代碼的分析

Class com.sosnoski.generics.PathDirectory details:

m_pathPairs is of parameterized type

com.sosnoski.generics.PairCollection

using types (Java.lang.String com.sosnoski.generics.DirInfo)

Class com.sosnoski.generics.DirInfo details:

m_files is of parameterized type

Java.util.List

using types (com.sosnoski.generics.FileInfo)

Class com.sosnoski.generics.FileInfo details:

m_name is of type Java.lang.String

m_lastModify is of type Java.util.Date

Class Java.util.Date details:

fastTime is of type long

cdate is of type sun.util.calendar.BaseCalendar$Date

Class sun.util.calendar.BaseCalendar$Date details:

cachedYear is of type int

cachedFixedDateJan1 is of type long

cachedFixedDateNextJan1 is of type long

m_directorIEs is of parameterized type

Java.util.List

using types (com.sosnoski.generics.DirInfo)

m_lastModify is of type Java.util.Date

Class com.sosnoski.generics.PairCollection details:

m_tValues is of parameterized type

Java.util.ArrayList

using types (T)

Class Java.util.ArrayList details:

elementData is array type E[]

size is of type int

m_uValues is of parameterized type

Java.util.ArrayList

using types (U)

  清單 9 的輸出給出了泛型類型是如何參數化使用的基本情況,包括為在 DirInfo 類中列出的 m_files 和 m_directorIEs 項指定的類型。但當涉及到 PairCollection 類(在底部)時,字段類型只是作為變量給出。對這個字段只顯示為變量的原因是由反射提供的泛型類型信息不處理替換 —— 而是由反射代碼的使用者處理泛型類中的替換。這項工作並不太困難,因為可以從清單 9 的輸出中進行猜測。這裡 m_tValues 展開的細節顯示 ArrayList 是用 “T” 類型參數化的,而嵌套的 ArrayList 展開顯示 elementData 字段是用類型 “E” 參數化的。要在每一個實例中正確關聯這些類型,需要在展開的每一階段跟蹤類型變量實際被替換的類型(如前所述,可用 java.lang.Class.getTypeParameters() 方法得到)。在這裡,這意味著在 PairCollection 展開中的 “T” 和 m_tValuesArrayList 展開中的 “E” 替換 Java.lang.String。我不再給出更多的清單,而是將變化細節留給您。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved