2009-12-12 147 views
17

我想用Class.getMethod()反射和自動裝箱的任何解決方案?

Class.getMethod(String name, Class... parameterTypes) 

找到我需要用給定的參數調用該方法,但顯然在Bug 6176992描述Java不包括自動裝箱那裏。所以如果我的反射類有一個帶有(String,int)簽名的方法,你仍然會得到一個帶{String.class,Integer.class}數組作爲參數的NoSuchMethodException。

有沒有這方面的溶劑?我可以想到的唯一方法就是調用getMethod()以及我不想要的原始和非原始類型的每種排列。

編輯:爲了更清楚:我很清楚原始類型類,但我沒有看到他們如何幫助解決我的問題。我的parameterTypes數組來自某處,我知道它只會返回非原始類型。我不能認爲界面只會用原始類型聲明,這正是我的問題:

public interface TestInterface() 
{ 
    public void doTest(Integer i1, int i2, double d3, Double d); 
} 

Class<?>[] classes = { Integer.class, Integer.class, Double.class, Double.class } 
// Due to autoboxing I should become the doTest method here, but it doesn't work 
TestInterface.class.getMethod("doTest", classes); 
+0

您知道方法的簽名是提前? – PSpeed 2009-12-12 21:27:19

+0

不,不是。那麼好的另一個解決方案是獲得具有該名稱的所有方法,並查看簽名是否與(un)盒裝類型相匹配。 – Daff 2009-12-12 21:30:03

回答

11

正如@Stephen C提到的,你唯一的希望就是自己去做搜索。他所有的注意事項都有效,但我認爲,只要呼叫者知道警告信號,一定的靈活性就可以覆蓋大部分細節問題......與使呼叫者始終處於特定的痛苦狀態無關。

對於代碼,實際上做這樣的事情,你可以看看這裏: http://meta-jb.svn.sourceforge.net/viewvc/meta-jb/trunk/dev/src/main/java/org/progeeks/util/MethodIndex.java?revision=3811&view=markup

的findMethod()調用的入口點,但它代表(經過一些緩存等),這種方法:

private Method searchForMethod(String name, Class[] parms) { 
    Method[] methods = type.getMethods(); 
    for(int i = 0; i < methods.length; i++) { 
     // Has to be named the same of course. 
     if(!methods[i].getName().equals(name)) 
      continue; 

     Class[] types = methods[i].getParameterTypes(); 

     // Does it have the same number of arguments that we're looking for. 
     if(types.length != parms.length) 
      continue; 

     // Check for type compatibility 
     if(InspectionUtils.areTypesCompatible(types, parms)) 
      return methods[i]; 
     } 
    return null; 
} 

InspectionUtils.areTypesCompatible()取兩個類型的列表,對它們的基元進行規範化,然後驗證其中一個對另一個是「可分配的」。所以它會處理你有一個Integer的情況,並且試圖調用一個int方法,以及你有一個String的情況,並且試圖調用一個帶Object的方法。它確實是而不是處理具有一個int並調用一個採用float的方法的情況。一定有一些特異性。

需要注意的是,上述方法只是按照方法順序搜索,所以如果有歧義,那麼選擇是任意的。到目前爲止,我從來沒有遇到過現實世界的問題。

下面是參考兼容性檢查: 公共靜態布爾areTypesCompatible(類[]目標,類[]來源){ 如果(targets.length =來源!。長度) 返回false;

for(int i = 0; i < targets.length; i++) { 
     if(sources[i] == null) 
      continue; 

     if(!translateFromPrimitive(targets[i]).isAssignableFrom(sources[i])) 
      return false; 
     } 
    return(true); 
} 

代碼是BSD和我的代碼段合法使用。如果你決定直接使用這個util包,最近的公開發布版本在這裏: https://meta-jb.svn.sourceforge.net/svnroot/meta-jb/trunk/dev/m2-repo/org/meta-jb/meta-jb-util/0.17.1/

而我只提到,因爲長期沒有捆綁下載,因爲我的大多數活躍用戶是maven用戶。我似乎更喜歡編寫代碼,而不是削減完整版本。 ;)

+0

感謝您的示例源代碼。我會嘗試一下代碼,並希望它不會太慢。希望這不是容易出錯的,但是如果我只是用一個嚴格的規則去反映類可能不包含的內容。 – Daff 2009-12-13 11:32:27

+0

匹配結果的這種早期返回的一個缺點是如果有兩種匹配方法,例如(String,int)和(String,Integer)Java總是採用相同的方法,但是很難確定搜索將返回哪種方法。 – 2009-12-13 15:02:12

+0

這是我提到的警告:「如果有歧義,那麼選擇是任意的。」總的來說,這是一致的,但我同意這很難預測。此外,我認爲它不是很清楚。當您通過反射進行調用時,即使您是通過查找基於「int」的方法開始的,您也總是傳遞「整數」。好的是,如果只有一個「int」或一個「Integer」,那麼您將以任何方式覆蓋。你有(String,int)和(String,Integer)的情況非常罕見......更罕見的是,它不會只調用另一個。 – PSpeed 2009-12-13 17:29:46

2

Integer.class表示Integer對象類型。 Integer.TYPE表示int基元類型。那樣有用嗎?

+0

那正是問題所在。 getClass()。getMethod(String name,Class ... parameterTypes)似乎並不關心整個自動裝箱事件,所以目前似乎沒有辦法找到參數可能變成(未)裝箱的方法。我不知道方法簽名以及getMethod的Class數組參數的外觀。 – Daff 2009-12-12 21:45:46

+0

有人會問你爲什麼要調用一個你不知道參數的方法......如果你描述你正在做的更多,或許一個更好的解決方案將呈現。 – TofuBeer 2009-12-12 21:52:55

+0

猜你是對的,我更新了我的問題。調用不知道參數的方法是通用調度機制的一部分。 – Daff 2009-12-12 22:04:35

4

是的,你需要使用Integer.TYPE或(等同)int.class

更新:「我的parameterTypes數組來自某處,我知道它只會返回非原始類型。」那麼,這就是這個「某個地方」的問題。如果他們沒有給你正確的方法簽名,那麼你將如何找到它?如果有兩種重載的方法,那麼只有其中一種需要原語,另一種則需要包裝類?那麼它選擇哪一個?我的意思是,如果你真的沒有選擇,那麼我想你可以循環所有的方法,並找到一個正確的名稱,並手動檢查所有的參數類型是否正確,或者是原始的等價物。

+0

我更新了我的問題,也許它使得它更清晰一些,爲什麼原始類型類不能幫助我。 – Daff 2009-12-12 22:08:47

2

反射方法查找並不像編譯器那麼複雜。我明白你爲什麼需要這樣的東西。假設在運行時你有一個方法名和一個對象數組作爲參數,並且你希望反射給你基於參數類型的確切方法,但它比這更復雜。例如:

void f(Integer i){..} 
void f(int i){...} 

當參數類型爲Integer時,您可以選擇哪一個?一個更棘手一個:

void f(Integer i, List l){...} 
void f(Object o, LinkedList l){...} 

編譯器提供了一組規則來選擇基於靜態信息的「最具體的方法」;如果它不能確定它會馬上提醒你。

你必須模擬編譯器並編寫算法來找出「最具體的方法」。 (哦,並考慮到自動裝箱,幾乎忘了!)

1

現在唯一的答案是編寫代碼來模擬Java編譯器的類型提升規則,反思性地選擇最合適的方法。自動裝箱和拆箱只是編譯器知道的類型提升的示例...

爲什麼Java反射API已不這樣做?我可以想到一些原因。

  • Java之前1。5,getMethod班級和朋友不明白怎麼辦(例如)intfloat的推廣。如果在1.5之前沒有需要,爲什麼現在呢?

  • 添加這種東西會使反射方法調用更慢。

  • 自動裝箱和取消裝箱可能會與非反射方法調用混淆。增加反射只會增加更多的混淆。

  • 升級規則的運行時實現將添加一類新的運行時錯誤情況,這些情況需要映射到異常並由用戶代碼進行診斷。這些將會特別棘手。例如,它將不得不處理與模糊方法調用等價的反思。

  • 向後兼容的總體要求意味着Sun必須將其作爲新方法來實現。他們不能改變當前方法的行爲,因爲這可能會破壞成千上萬的客戶的現有應用程序。

最後有一點與OP的用例有關(如上所述)。 OP表示他的代碼不知道是否期望(例如)在目標類上使用intInteger參數的方法。假設編寫目標類的人同時提供了重載......並且語義是微妙的(或不可靠的)不同的呢?不管你做什麼,都會出現OP代碼選擇客戶端不期望的超載的情況。壞。

IMO,對於OP的代碼來說,施加一些簡單的API規則會更好,這些規則說明何時使用原語與包裝器是正確的。

+0

謝謝,考慮一下,這確實有道理。也許我應該制定規則,即反射類只能包含被包裝的類型,如果他們想被調用。 – Daff 2009-12-13 11:28:57

0

您可以在反射調用中添加一些額外的邏輯,它嘗試將Integer.class(或其他)轉換爲相應的原始類,然後重複查找該方法,直到獲得匹配。如果你有Apache Commons Lang,那麼wrapperToPrimitive方法會爲你做這個對話,但是自己寫這個對話是微不足道的。

  1. 執行getMethod像往常一樣
  2. 如果沒有發現什麼,然後尋找具有相應的原語
  3. 對於每個組合的任何參數類型,執行另一個查找到的東西棒。

不夠優雅,對於有很多原始參數的方法,它甚至可能會很慢。

9

如果您擁有版本> = 2.5的commons-lang,則可以使用MethodUtils.getMatchingAccessibleMethod(...)來處理裝箱類型問題。

+0

工程像魅力。 – rlegendi 2012-09-28 15:14:42

0

嘗試使用Class.isPrimitive()來確定它是否爲原始類型,如果是,則使用反射來檢索TYPE字段並檢查它是否相等。因此,在非常自由的僞代碼中:

for(Method m:getDeclaredMethods()) 
    for(Class c:m.getParameterTypes() && Class desired:desiredMethodArgTypes) 
    if(c.isAssignableFrom(desired)) 
     //matches 
    if(c.isPrimitive() && c==desired.getDeclaredField("TYPE").get(desiredObject)) 
     //matches 
+0

在其他新聞中,我有閱讀障礙症 - 多年以來(直到必須用手在上面輸入)我認爲它是'isAssignableForm()' – 2011-03-24 22:06:04