泛型增加了 classworking 工具可以使用的類(lèi)型信息
Java™ 5 擴展了 Java 語(yǔ)言類(lèi)型系統以支持類(lèi)、方法和值的參數化類(lèi)型。參數化的類(lèi)型通過(guò)確保使用正確的類(lèi)型及消除從源代碼進(jìn)行類(lèi)型轉換提供了重要的編譯時(shí)好處。除了這些編譯時(shí)好處,類(lèi)型信息對于 classworking 工具操縱 Java 代碼也有幫助。在本文中,JiBX 首席開(kāi)發(fā)員 Dennis Sosnoski 分析了如何用反射深入參數化類(lèi)型的內部,并充分展示了 Java 5 應用程序數據結構的優(yōu)勢。
許多工具都是圍繞使用 Java 反射而設計的,它們的用途包括從用數據值填充 GUI 組件到在運行的應用程序中動(dòng)態(tài)裝載新功能。反射對于在運行時(shí)分析數據結構特別有用,許多在內部對象結構與外部格式(包括 XML、數據庫和其他持久化格式)之間轉換的框架都基于對數據結構的反射分析。
使用反射分析數據結構的一個(gè)問(wèn)題是標準 Java 集合類(lèi)(如 java.util.ArrayList)對于反射來(lái)說(shuō)總是“死胡同(dead-end)” —— 到達一個(gè)集合類(lèi)后,無(wú)法再訪(fǎng)問(wèn)數據結構的更多細節,因為沒(méi)有關(guān)于集合中包含的項目類(lèi)型的信息。Java 5 改變了這一情況,它增加了對泛型的支持,將所有集合類(lèi)轉換為支持類(lèi)型的泛型形式。Java 5 還擴展了反射 API ,支持在運行時(shí)對泛型類(lèi)型信息進(jìn)行訪(fǎng)問(wèn)。這些改變使反射可以比以往更深入地挖掘數據結構。
包裝之下代碼
許多文章討論了 Java 5 的泛型功能的使用(包括 參考資料 中的鏈接)。對于本文,假定您已經(jīng)了解泛型的基本知識。我們首先使用一些示例代碼,然后直接討論如何在運行時(shí)訪(fǎng)問(wèn)泛型信息。
作為使用泛型的一個(gè)例子,我準備使用一個(gè)表示一組路徑中的目錄和文件的數據結構。清單 1 給出了這個(gè)數據結構根類(lèi)的代碼。PathDirectory 類(lèi)取路徑 String 數組作為構造函數參數。這個(gè)構造函數將每一個(gè)字符串解釋為目錄路徑,并構造一個(gè)數據結構以表示這個(gè)路徑下面的文件和子目錄。處理每一路徑時(shí),這個(gè)構造函數就將這個(gè)路徑和這個(gè)路徑的數據結構加到一個(gè)成對集合(pair collection)中。
清單 1. 目錄信息集
public class PathDirectory implements Iterable<String>{ private final PairCollection<String, DirInfo> m_pathPairs; public PathDirectory(String[] paths) { m_pathPairs = new PairCollection<String, DirInfo>(); 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<String, DirInfo>.PairIterator iterator() { return m_pathPairs.iterator(); } public static void main(String[] args) { PathDirectory inst = new PathDirectory(args); PairCollection<String, DirInfo>.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<T,U> 的代碼。這個(gè)泛型類(lèi)處理成對的值,類(lèi)型參數給出了這些對中項目的類(lèi)型。它提供了一個(gè) add() 方法向集合中加入一個(gè)元組(tuple),一個(gè) clear() 方法清空集合中所有元組,一個(gè) iterator() 方法返回遍歷集合中所有對的迭代器。內部 PairIterator 類(lèi)實(shí)現由后一個(gè)方法返回的特殊迭代器,它定義了一個(gè)額外的 matching() 方法,這個(gè)方法用于得到由標準 next() 方法返回的值的配對(第二個(gè))值。
清單 2. 泛型對集合
public class PairCollection<T,U> implements Iterable<T>{ // code assumes random access so force implementation class private final ArrayList<T> m_tValues; private final ArrayList<U> m_uValues; public PairCollection() { m_tValues = new ArrayList<T>(); m_uValues = new ArrayList<U>(); } 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<T> { 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<T,U> 對于包含值的實(shí)際集合在內部使用泛型。它用第一個(gè)參數類(lèi)型實(shí)現了 java.lang.Iterable 接口,從而可以直接在新型 for 循環(huán)中使用以遍歷每對中的第一個(gè)值。不幸的是,在使用新型 for 循環(huán)時(shí),無(wú)法訪(fǎng)問(wèn)實(shí)際的迭代器,因而無(wú)法獲取每一對的第二個(gè)值。這就是為什么 清單 1 中的 main() 測試方法使用 while 循環(huán)而不是一個(gè)新的 for 循環(huán)。
清單 3 給出了包含目錄和文件信息的一對類(lèi)的代碼。DirInfo 類(lèi)使用有類(lèi)型的 java.util.List 集合表示普通文件和目錄的子目錄。構造函數將這些集合創(chuàng )建為不可修改的列表,使得它們可以安全地直接返回。FileInfo 類(lèi)更簡(jiǎn)單,只包含文件名和最后修改日期。
清單 3. 目錄和文件數據類(lèi)
public class DirInfo{ private final List<FileInfo> m_files; private final List<DirInfo> m_directories; private final Date m_lastModify; public DirInfo(File dir) { m_lastModify = new Date(dir.lastModified()); File[] childs = dir.listFiles(); List<FileInfo> files = new ArrayList<FileInfo>(); List<DirInfo> dirs = new ArrayList<DirInfo>(); 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<DirInfo> getDirectories() { return m_directories; } public List<FileInfo> 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/businessDirectory /home/dennis/bin has 31 files and 0 child directoriesDirectory /home/dennis/xtools has 0 files and 3 child directoriesDirectory /home/dennis/docs/business has 34 files and 34 child directories
|
泛型反射
泛型是在 Java 平臺上作為編譯時(shí)轉換實(shí)現的。編譯器實(shí)際上生成與使用非泛型源代碼時(shí)相同的字節指令,插入運行時(shí)類(lèi)型轉換以在每次訪(fǎng)問(wèn)時(shí)將值轉換為正確的類(lèi)型。盡管是相同的字節碼,但是類(lèi)型參數信息用 一個(gè)新的簽名(signature) 屬性記錄在類(lèi)模式中。JVM 在裝載類(lèi)時(shí)記錄這個(gè)簽名信息,并在運行時(shí)通過(guò)反射使它可用。在這一節,我將深入挖掘反射 API 如何使類(lèi)型信息可用的細節。
類(lèi)型反射接口
通過(guò)反射訪(fǎng)問(wèn)類(lèi)型參數信息有些復雜。首先,需要有一個(gè)具有所提供類(lèi)型信息的字段(或者其他可提供類(lèi)型的辦法,如方法參數或者返回類(lèi)型)。然后可以用 Java 5 新增的 getGenericType() 方法從這個(gè)字段的 java.lang.reflect.Field 實(shí)例提取特定于泛型的信息。這個(gè)新方法返回一個(gè) java.lang.reflect.Type 實(shí)例。
惟一的問(wèn)題是 Type 是一個(gè)沒(méi)有方法的接口。在實(shí)例化后,需要檢查擴展了 Type 的子接口以了解得到的是什么(以及如何使用它)。Javadocs 列出了四種子接口,我會(huì )依次介紹它們。為了方便,我在清單 5 中給出了接口定義。它們都包括在 java.lang.reflect 包中。
清單 5. Type 子接口
interface GenericArrayType extends Type { Type getGenericComponentType();}interface ParameterizedType extends Type { Type[] getActualTypeArguments(); Type getOwnerType(); Type getRawType();}interface TypeVariable<D extends GenericDeclaration> extends Type { Type[] getBounds(); D getGenericDeclaration(); String getName();}interface WildcardType extends Type { Type[] getLowerBounds(); Type[] getUpperBounds();}
|
java.lang.reflect.GenericArrayType 是第一個(gè)子接口。這個(gè)子接口提供了關(guān)于數組類(lèi)型的信息,數組的組件類(lèi)型可以是參數化的,也可以是一個(gè)類(lèi)型變量。只定義了一個(gè)方法 getGenericComponentType(),它返回數組組件 Type。
java.lang.reflect.ParameterizedType 是 Type 的第二個(gè)子接口。它提供了關(guān)于具有特定類(lèi)型參數的泛型類(lèi)型的信息。這個(gè)接口定義了三個(gè)方法,其中最讓人感興趣的(對于本文來(lái)說(shuō))是 getActualTypeArguments() 方法。這個(gè)方法返回一個(gè) (drum role)數組 . . .還有更多的 Type 實(shí)例。返回的 Type 表示原來(lái)(未參數化的)類(lèi)型的實(shí)際類(lèi)型參數。
Type 的第三個(gè)子接口是 java.lang.reflect.TypeVariable<D extands GenericDeclaration>。這個(gè)接口給出了表示一個(gè)參數類(lèi)型的變量(如這個(gè)類(lèi)型名中變量 "D")的細節。這個(gè)接口定義了三個(gè)方法:getBounds(),它返回(您猜) Type 實(shí)例數組;getGenericDeclaration(),它返回對應于類(lèi)型變量聲明的 java.lang.reflect.GenericDeclaration 接口的實(shí)例;getName(),它返回類(lèi)型變量在源代碼中使用的名字。這些方法都需要進(jìn)一步說(shuō)明。因此我將逐一分析它們。
由 getBounds() 方法返回的類(lèi)型數組定義對于變量的類(lèi)型所施加的限制。這些限制在源代碼中作為在模板變量中以 extends B(其中 “B” 是某種類(lèi)型)的格式添加的子句進(jìn)行聲明。很方便, java.lang.reflect.TypeVariable<D extends GenericDeclaration> 本身就給出了這種形式的上界定義的一個(gè)例子 —— java.lang.reflect.GenericDeclaration 是類(lèi)型參數 “D” 的上界,意味著(zhù) “D” 必須是擴展或者實(shí)現 GenericDeclaration 的類(lèi)型。
getGenericDeclaration() 方法提供了一種訪(fǎng)問(wèn)聲明了 TypeVariable 的 GenericDeclaration 實(shí)例的方式。在標準 Java API 中有三個(gè)類(lèi)實(shí)現了 GenericDeclaration:java.lang.Class、java.lang.reflect.Constructor 和 java.lang.reflect.Method。這三個(gè)類(lèi)是有意義的,因為參數類(lèi)型只能在類(lèi)、構造函數和方法中聲明。GenericDeclaration 接口定義了一個(gè)方法,它返回在聲明中包含的 TypeVariable 的數組。
getName() 方法只是返回與源代碼中給出的完全一樣的類(lèi)型變量名。
java.lang.reflect.WildcardType 是 Type 的第四個(gè)(也是最后一個(gè))子接口。WildcardType 只定義了兩個(gè)方法,返回通配類(lèi)型的下界和上界。在前面,我給出了上界的一個(gè)例子,下界也類(lèi)似,但是它們是通過(guò)指定一種類(lèi)型而定義的,提供的類(lèi)型必須是它的超接口或者超類(lèi)。
對一個(gè)例子的反射
我在上一節中描述的反射接口提供了解碼泛型信息的鉤子,但是確定它們有點(diǎn)難度 —— 不管從哪兒開(kāi)始,每件事都像是循環(huán)并回到 java.lang.reflect.Type。為了展示它們是如何工作的,我將利用 清單 1 代碼中的一個(gè)例子并對它進(jìn)行反射。
首先,我嘗試訪(fǎng)問(wèn) 清單 1 的 m_pathPairs 字段的類(lèi)型信息。清單 6 中的代碼得到這個(gè)字段的泛型類(lèi)型,檢查結果是否為所預期的類(lèi)型,然后列出原始類(lèi)型和參數化類(lèi)型的實(shí)際類(lèi)型參數。在清單 6 的最后以粗體顯示了運行這段代碼的輸出:
清單 6. 第一個(gè)反射代碼
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<String, DirInfo> 類(lèi)型,它匹配反射所訪(fǎng)問(wèn)的類(lèi)型信息。不過(guò)深入實(shí)際的參數化類(lèi)型定義會(huì )有些復雜;返回的 Type 實(shí)例是一個(gè)沒(méi)有實(shí)現任何 Type 子接口的 java.lang.Class 對象。幸運的是,Java 5 Class<T> 類(lèi)本身提供了一個(gè)深入泛型類(lèi)定義的細節的方法。這個(gè)方法是 getTypeParameters(),它返回一個(gè) TypeVariable<Class<T>> 數組。在清單 7 中,我修改了 清單 6 的代碼以使用這個(gè)方法,得到的結果同樣以粗體顯示在代碼中:
清單 7. 深入參數化類(lèi)型
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 的結果顯示了解碼的結構。實(shí)際的類(lèi)型參數可以由泛型類(lèi)定義的類(lèi)型變量匹配。在下一節,我將做此工作作為遞推泛型解碼方法的一部分。
泛型遞推
在上一節,我快速完成了訪(fǎng)問(wèn)泛型信息的反射方法?,F在我將用這些方法構建一個(gè)解釋泛型的遞推處理程序。清單 8 給出了相關(guān)的代碼:
清單 8. 遞推泛型分析
public class Reflect{ private static HashSet<String> s_processed = new HashSet<String>(); 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 中的代碼使用兩個(gè)相互遞推的方法進(jìn)行實(shí)際的分析。analyze() 方法取一個(gè)類(lèi)作為參數,通過(guò)對每個(gè)字段的定義進(jìn)行必要的處理展開(kāi)這個(gè)類(lèi)。describe() 方法打印特定字段的類(lèi)型信息的描述,對在這一過(guò)程中它遇到的每一個(gè)類(lèi)調用 analyze()。每個(gè)方法還有一個(gè)給出當前縮進(jìn)字符串的參數,它使每一級類(lèi)嵌套都縮進(jìn)一些空間。
清單 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 的輸出給出了泛型類(lèi)型是如何參數化使用的基本情況,包括為在 DirInfo 類(lèi)中列出的 m_files 和 m_directories 項指定的類(lèi)型。但當涉及到 PairCollection 類(lèi)(在底部)時(shí),字段類(lèi)型只是作為變量給出。對這個(gè)字段只顯示為變量的原因是由反射提供的泛型類(lèi)型信息不處理替換 —— 而是由反射代碼的使用者處理泛型類(lèi)中的替換。這項工作并不太困難,因為可以從清單 9 的輸出中進(jìn)行猜測。這里 m_tValues 展開(kāi)的細節顯示 ArrayList 是用 “T” 類(lèi)型參數化的,而嵌套的 ArrayList 展開(kāi)顯示 elementData 字段是用類(lèi)型 “E” 參數化的。要在每一個(gè)實(shí)例中正確關(guān)聯(lián)這些類(lèi)型,需要在展開(kāi)的每一階段跟蹤類(lèi)型變量實(shí)際被替換的類(lèi)型(如前所述,可用 java.lang.Class.getTypeParameters() 方法得到)。在這里,這意味著(zhù)在 PairCollection 展開(kāi)中的 “T” 和 m_tValuesArrayList 展開(kāi)中的 “E” 替換 java.lang.String。我不再給出更多的清單,而是將變化細節留給您。
更多的泛型內容
我已在本文中展示了如何在運行時(shí)挖掘已編譯類(lèi)的泛型類(lèi)型信息(至少是基本的,我忽略了像內部類(lèi)這樣的復雜情況以及泛型中一些更復雜的結構)。
作為一個(gè)示例應用程序,我準備使用泛型類(lèi)型信息改進(jìn)我的 JiBX XML 數據綁定框架中提供的默認綁定生成器?,F在綁定生成器不知道在 Java 集合(或者其他無(wú)類(lèi)型的引用)中出現的是什么樣的內容,因此生成器讓用戶(hù)修改生成的綁定并增加相應的內容;加入泛型反射代碼后,對于使用 Java 5 的用戶(hù),生成器將可以從泛型中直接得到類(lèi)型信息。
但是在 JVM 中裝載類(lèi)以訪(fǎng)問(wèn)泛型類(lèi)型信息并不總是方便的。對于 JiBX,處理類(lèi)時(shí)最重要的部分是向編譯的類(lèi)表示中加入字節碼。為此,JiBX 使用了一個(gè)字節碼操縱框架(在 JiBX 中是 BCEL,在 JiBX 2.0 中改為 ASM)。對于 JiBX 來(lái)說(shuō),幸運的是,ASM 框架包含在解析二進(jìn)制類(lèi)表示時(shí)訪(fǎng)問(wèn)同樣的類(lèi)型信息的鉤子,而且可以在生成新類(lèi)時(shí)添加泛型類(lèi)型信息。下個(gè)月,我將介紹 ASM 的分析方式并與在本月討論的反射支持進(jìn)行比較。