Field類
Field類中定義了一些方法,可以用來查詢字段的類型以及設置或讀取字段的值。將這些方法與繼承而來的member方法結合在一起.就可以使我們能夠找出有關字段聲明的全部信息,并且能夠操縱某個特定對象或類的字段。
getGenericType方法返回表示字段的聲明類型的Type實例。對于像String或int這樣的平凡類型,該方法將返回與其相關聯的Class對象,例如String.class和int.classo對于像List < Stri ng>這樣的參數化類型,該方法將返回Parameterizedrype的實例,例如,對像T這樣的類型,該方法將返回Typevariable實例。
遺留下來的getType方法將返回字段的類型的Class對象。對于平凡類型,該方法的行為與getGenericType方法的相同。如果字段的聲明類型是參數化類型,那么getType方法將返回參數化類型的擦除所對應的Class對象,即原始類型的Class對象。例如,對于聲明為List < Stri ng>的對象,getType將返回Li St. class的。如果字段的聲明類型是類型變量,那么getType方法將返回類型變量的擦除所對應的class對象。例如,假設有一個類FOO<丁>,對于其聲明為T類型的字段,get丁ype將返回object.
class對象。如果FOO被聲明為FOo<下extends Number >,那么get下ype將返回 Number.class.
我們可以使用isEnumConstant方法查詢一個字段是否是枚舉常量,也可以使用get和set方法來獲取和設置字段的值。這些接受object引元并返回Obj ect值的方法都有一種通用形式,以及一些可以直接處理基本類型的更加特化的形式。所有這些方法都要接受一個引元,用來指定所要操作的對象。對于靜態字段,將忽略這個對象引元,所以此時也可以將其設
置為null。下面的方法將打印一個對象的short型字段的值:
1
2
3
4
5
6
7
8
9
10
11
|
public static void printShortField(Object o, String name) throws NoSuchFieldException,IllegalAccessException { Field field=o.getClass().getField(name); short value=(Short) field.get(o); System.out.println(value); |
get方法的返回值可以是這個字段所引用的任何對象,如果該字段是基本類型,那么該方法將返回恰當類型的包裝器類對象。對于我們的”hort型字段,get方法將返回包含該字段值的short類型的對象,而在將它賦值給本地變量value時,該對象值會自動進行拆箱轉換。
set方法的使用也是類似的。將short型字段設置為所提供的值的方法看起來可能像下面這樣:
1
2
3
4
5
6
7
8
9
|
public static voi setShortField(Object o,String name, short nv) throws NoSuchFieldException,IllegalAccessException Field field= 0 .getClass().getField(name); field .set(o .nv); |
雖然set接受的是Object類型的參數,但是我們可以直接傳遞一個short型的值,并用包裝轉換將其包裝為short類型的對象。
在上面的方法中,如果指定對象的域是不可訪問的,并且這種訪問權限控制是強制執行的,那么就會拋出IllegalACcessException異常;如果傳遞的對象與該域的類型不同,就會拋出illegalArgumentException異常;如果該域是非靜態的且傳遞的對象引用是null,就會拋出NullPointerException異常;訪問靜態域可能會要求對類進行初始化,所以該方法也會拋出ExceptionInInitializerError異常。
Field類還有特定的用來獲取和設置基本類型的方法,例如,我們可以在Field對象上調用getPrimitive7ype和set Primitive7ype,其中Primitive7ype是(首字母大寫的)基本類型名。get方法可用于下面的語句:
1
|
short value=field.getshort(o); |
而set方法可用于下面的語句:
1
|
field.setshort(o, nv); |
用以上兩種方式聲明的語句中可以避免使用包裝器類對象。
Field類實現了AnnotatedElement接口,所以我們也可以像16.2節那樣查詢應用于域
上的注解。
憑借上面介紹的方法,我們可以將Field對象用作操縱任意值的一種方式,但是我們應該盡量避免使用它。因為Java語言會在程序的編譯期盡可能多地捕獲編程錯誤,所以在我們編寫代碼時,使用的諸如「ield對象這樣的間接方法越少,那么在將它們編譯成代碼之前,就可以防止更多的錯誤。而且,我們可以看到,在前面的代碼中,要想知道到底會發生什么,與在普通的語法中直接使用域名的情況相比,我們花費在閱讀代碼上的精力顯然大了許多。
Final字段
在通常情況下,對聲明為final的字段進行設置將會導致拋出IllegalACcessException
異常,這是我們所能預期的,因為final字段的值是永遠不會改變的。但是有些特殊情況—例如在定制的反序列化(見20.8.4節)中,改變final字段的值就是有意義的,我們只有在實例字段上才能通過反射實現這一點,并且前提是在該Field對象上已經調用過了setAccessible(true)。注意,可以成功調用setAccessible(true)是不夠的,必須確實調用過它。
這種能力是為高度特化的上下文提供的,并非用于通用目的,我們介紹它僅僅是為了保持內容的完整性。如果脫離了特定的上下文,例如定制的反序列化,那么改變final字段的值可能會導致意外的甚至是災難性的后果。在這些上下文之外,不能保證對final字段的改變是可見的。即便是在這樣的上下文中,在使用這項技術編碼時也必須保證安全機制不會阻礙代碼的執行。改變值為常量變量(見2.2.3節)的final字段將會導致此改變不可見,除非通過使用反射來實現這種修改。
Method類
method類和它從member類繼承而來的方法使得我們可以獲得方法聲明的完整信息:
" public Type getGenericReturnTypeO:該方法返回的是目標方法的返回類型的Type對象。如果目標方法被聲明為返回void,則該方法返回void.classo
" public Type[] getGenericParameterTypes():該方法返回目標方法所有參數類型的Type對象數組,這些Type對象將按照參數的聲明順序存儲于在數組中。如果目標方法沒有任何參數,則該方法返回一個空數組。
.publ i c Type [] getGeneri caccepti onTypes Q:該方法返回在throws子句中列出的所有異常類型的Type對象數組,這些Type對象將按照異常的聲明順序存儲在數組中。
如果目標方法沒有聲明任何異常,則該方法返回一個空數組。
Java還提供了getReturnType,getParameterTypes和getExceptionTypes方法,用來返回Cl as”對象而不是Type對象。就像在使用Field.getType時,參數化類型和類型變量是由它們的擦除所對應的Class對象表示的。
method類實現了AnnotatedElement,并且我們可以像16.2節所討論的那樣去查詢應用于方法上的注解。另外,Method類還提供了getParameterAnnotations,用來提供對應用于方法參數上的注解進行訪問。getParameterAnnotations方法可以返回Annotation數組,其中最外層數組的每一個元素都與方法的參數相對應;如果某個參數沒有任何注解,則該方法為這個參數返回一個長度為0的Annotation數組。如果method對象所表示的方法自身就是一個注解元素,那么getDefaultvalue方法將返回一個表示該元素默認值的Object對象;如果method對象本身不是注解元素或者它沒有默認值,則該方法將返回null.Method類也實現了GenericDeclaration,因此定義了getTypeParameters方法,該方法將返回一個Typevariable對象數組。如果給定的method對象表示的不是泛型方法,該方法將返回一個空數組。
我們可以使用isvarArgs方法來檢查某個method對象是否是一個可變引元方法,而i sBridge方法可以用來檢查它是否是一個橋接方法
Method對象最有趣的用法就是反射地調用它自己:
.public object invoke(object onThis,object…args)throws IllegalACcessException,IllegalArgumentException,工nvocation下argetException:該方法在onThis對象上調用method對象定義的方法,并用args的值來設置被調用方法的參數。對于非靜態方法,onThis的實際類型就確定了將要調用方法的哪種實現,而對于靜態方法,onThis會被忽略,并且通常會設置為null. args值的數量必須和被調用方法的實際參數數量相同,并且這些值的類型必須全部都可賦值給那些被調用方法的參數;否則,我們將會得到工llegalArgumentException異常。請注意,可變引元方法的最后一個參數是一個數組,所以我們必須用實際想要傳遞的“可變”引元來填充該數組。如果我們想調用我們沒有訪問權限的方法,該方法就會拋出IllegalACcessException異常。如果被調用方法不是on下his對象的方法,該方法會拋出工llegalArgumentExcepti on異常。如果onThis為null并且是非靜態的,該方法就會拋出NO 1PointerException異常。如果這個 method對象表示的是靜態方法,并且聲明這個靜態方法的類仍處于待初始化狀態,該方法就會拋出ExceptionIn工nitializerError異常。如果被調用法出異冪,談萬法就會拋出InvocationTargetException異常。
當我們使用invoke方法時,可以直接傳遞基本類型,也可以使用合適的包裝器類。包裝器類表示的類型必須可賦值給方法所聲明的參數類型。我們可以使用Long,Float或Double來包裝double類型的引元,但是不能用Double來包裝long或float類型的引元,因為double不是可賦值給long或們oat的。對invoke方法返回的object的處理方法和Field.get一樣,都是返回對應于它們的包裝器類的基本類型。如果方法聲明為void, invoke方法將返回null,
簡單地說,就是我們在用invoke來調用方法時,只能使用在Java語言中合法的與其參數
具有相同類型和值的引元。例如,下面的調用
1
|
return str.indexof(".”, 8 ); |
可以用反射寫成如下形式:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
Throwable fa們ure; try { Method indexM=String. class . getMethod( "index0f" ,String. class , int . class ); return (Integer) indexM.invoke(str,”,”, 8 ); } catch (NoSuchMethodException e){ failure=e; } catch (InvocationTargetException e){ fa們ure=e .getCause(); } catch (IllegalAccessException e){ failure=e; } throw fa們ure; |
雖然編譯器對于直接調用所做的安全性檢查,在使用反射的情況下,只能在運行時使用invoke時進行,但是基于反射的代碼確實擁有與直接調用的代碼在語義上等效的安全性檢查。訪問權限檢查可能會以略為不同的方式執行—安全管理器可能會拒絕訪問我們的包中的某個方法,即使我們可以直接調用該方法。
當我們可以使用這種形式的調用時,我們有充分的理由去避免它。但是如果我們在編寫調試器或其他需要將用戶輸入解釋為對對象操作的泛型應用時使用invoke或get/set方法,就會顯得很合理。method對象在某種程度上可以當作類似其他語言中的方法指針來使用,但是我們有更好的工具,尤其是接口、抽象類和嵌套類,可以用來處理那些通常在其他語言中用方法指針解決的問題。