2009-12-12 6 views
17

voglio usareQualche soluzione per la riflessione e l'autoboxing di Class.getMethod()?

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

per trovare il metodo che ho bisogno di richiamare con i parametri indicati, ma a quanto pare come descritto nel Bug 6176992 Java non comprende autoboxing lì. Quindi se la mia classe riflessa ha un metodo con una firma (String, int), si ottiene comunque un NoSuchMethodException con un array {String.class, Integer.class} come un paremeter.

C'è qualche soluzione per questo? L'unico modo in cui potrei pensare di chiamare getMethod() con ogni permutazione di tipi primitivi e non primitivi che non voglio veramente fare.

Modifica: Per rendere più chiaro: sono ben consapevole delle classi di tipi primitivi, ma non vedo come potrebbero aiutare a risolvere il mio problema. Il mio array parameterTypes viene da qualche parte e so che restituirà solo tipi non primitivi. Non posso pensare che l'interfaccia sarà dichiarato solo con i tipi primitivi e questo è esattamente il mio problema:

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

Sai qual è la firma del metodo in anticipo? – PSpeed

+0

No, non proprio. Bene, un'altra soluzione sarebbe quella di ottenere tutti i metodi con quel nome e verificare se la firma corrispondesse ai tipi (un) in scatola. – Daff

risposta

11

Come cita @Stephen C, la tua unica speranza è di fare la ricerca da solo. Tutti i suoi accorgimenti sostengono, ma direi che un po 'di flessibilità farebbe molto per coprire la maggior parte dei trucchi fin tanto che i chiamanti fossero consapevoli delle avvertenze ... invece di rendere i chiamanti sempre dolorosamente specifici.

per il codice che fa effettivamente qualcosa di simile si può guardare qui: http://meta-jb.svn.sourceforge.net/viewvc/meta-jb/trunk/dev/src/main/java/org/progeeks/util/MethodIndex.java?revision=3811&view=markup

L'() chiamata findMethod è il punto di ingresso, ma delega (dopo un po 'di caching, ecc) per questo metodo:

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() accetta due elenchi di tipi, normalizza i loro primitivi e quindi verifica che uno sia "assegnabile" all'altro. Quindi gestirà il caso in cui si dispone di un intero e si sta tentando di chiamare un metodo che accetta int e il caso in cui si dispone di una stringa e si sta tentando di chiamare un metodo che accetta Object. È non gestire il caso di avere un int e chiamando un metodo che prende float. Ci deve essere alcune specificità.

L'unica avvertenza è che il metodo precedente cerca solo nell'ordine dei metodi, quindi se ci sono ambiguità la selezione è arbitraria. Non ho mai incontrato un problema del mondo reale, finora nella pratica.

Ecco il controllo di compatibilità per riferimento: public static areTypesCompatible booleana (classe obiettivi [], fonti Class []) {if ( targets.length = fonti!.lunghezza) return 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); 
} 

Il codice è BSD e mio, quindi i frammenti sono legali da utilizzare. Se decidi di utilizzare questo pacchetto util direttamente la versione pubblica più recente è qui: https://meta-jb.svn.sourceforge.net/svnroot/meta-jb/trunk/dev/m2-repo/org/meta-jb/meta-jb-util/0.17.1/

E lo accenno solo perché non c'è stato un download in bundle da molto tempo dalla maggior parte dei miei utenti attivi sono utenti esperti. Mi sembra di essere più appassionato di scrivere il codice piuttosto che tagliare i rilasci completi. ;)

+0

Grazie per la fonte di esempio. Proverò il codice e spero che non sarà troppo lento. Spero che non sia soggetto a errori, ma se andrò semplicemente con un set di regole rigoroso su ciò che le classi riflesse potrebbero non contenere. – Daff

+0

Uno svantaggio di questo ritorno anticipato dei risultati della corrispondenza è se hanno due metodi di corrispondenza, ad es. (String, int) e (String, Integer) Java avrebbe sempre utilizzato lo stesso metodo, tuttavia sarebbe difficile determinare quale metodo restituirebbe la ricerca. –

+0

Questa è l'avvertenza che ho menzionato: "se ci sono ambiguità, la selezione è arbitraria". In generale è coerente, ma sono d'accordo è difficile da prevedere.Inoltre, direi che non è comunque chiaro. Quando si effettuano chiamate tramite la reflection, si passa sempre un "Integer" anche se si inizia cercando un metodo basato su "int". La cosa bella è che se c'è solo un "int" o un "Integer" sei coperto in entrambi i modi. I casi in cui hai (String, int) e (String, Integer) sono molto rari ... più raro che uno non chiami semplicemente l'altro. – PSpeed

2

Integer.class rappresenta il tipo di oggetto Integer. Integer.TYPE rappresenta il tipo primitivo int. Funziona?

+0

Beh, questo è esattamente il problema. getClass(). getMethod (String name, Class ... parameterTypes) non sembra interessare l'intera cosa di autoboxing, quindi attualmente non sembra essere un modo per trovare un metodo in cui i parametri possano diventare (un) in scatola. Non conosco né la firma del metodo né come apparirà il parametro della matrice di classe per getMethod. – Daff

+0

Si potrebbe chiedere perché si sta chiamando un metodo in cui non si conoscono i parametri ... se si descrive cosa si sta facendo un po 'più forse una soluzione migliore si presenterà. – TofuBeer

+0

Indovina che hai ragione, ho aggiornato la mia domanda. Chiamare un metodo in cui non conosco i parametri fa parte di un meccanismo di dispacciamento generico. – Daff

4

Sì, è necessario utilizzare Integer.TYPE o (equivalentemente) int.class.

Aggiornamento: "Il mio array parameterTypes viene da qualche parte e so che restituirà solo tipi non primitivi." Bene, allora questo è il problema di "qualche parte". Se non ti danno la firma appropriata del metodo che vogliono, allora come lo troverai? Cosa succede se ci sono due metodi di overload, che differiscono solo in uno di essi prende una primitiva e l'altro prende la classe wrapper? Quale sceglie allora? Voglio dire, se davvero non hai scelta, suppongo che potresti semplicemente scorrere tutti i metodi, e cercarne uno con il nome giusto, e controllare manualmente tutti i tipi di parametri per vedere se sono corretti o se sono gli equivalenti primitivi.

+0

Ho aggiornato la mia domanda, forse rende un po 'più chiaro il motivo per cui le classi di tipi primitive non mi aiutano. – Daff

2

Il metodo di riflessione non è sofisticato come quello del compilatore. Capisco perché hai bisogno di qualcosa del genere. Supponiamo che in fase di esecuzione si disponga di un nome di metodo e di una matrice di oggetti come argomenti e si desideri che il reflection fornisca il metodo esatto in base ai tipi di argomenti, ma è più complicato di così. ad esempio:

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

quando l'argomento è di tipo Integer, quale si può scegliere? Uno ancora più complicato:

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

il compilatore ha un insieme di regole per scegliere il "metodo più specifico" basato su informazioni statiche; se non può determinarlo, ti avviserà subito.

devi simulare il compilatore e scrivere un algoritmo per scoprire il "metodo più specifico". (oh, e con considerazione dell'auto-boxing, quasi lo dimenticavo!)

1

L'unica risposta al momento è scrivere codice per simulare le regole di promozione del tipo del compilatore Java, in modo da selezionare in modo riflessivo il metodo più appropriato. Autoboxing e unboxing sono solo esempi di promozioni di tipo che il compilatore sa ...

Perché le API riflettenti Java non lo fanno già? Posso pensare a una serie di ragioni.

  • Prima di Java 1.5, la classe getMethod e gli amici non hanno capito come fare (ad esempio) la promozione di int a float. Se non ci fosse bisogno di pre-1.5, perché adesso?

  • L'aggiunta di questo tipo di materiale renderà il metodo di riflessione ancora più lento.

  • Autoboxing e unboxing possono confondere con il richiamo del metodo non riflettente. Aggiungere riflessioni aggiungerà solo più confusione.

  • L'implementazione runtime delle regole di promozione aggiungerà una classe di nuovi casi di errore di runtime che devono essere mappati alle eccezioni e diagnosticati dal codice utente. Questi saranno particolarmente difficili. Ad esempio, dovrebbe trattare l'equivalente riflessivo di una chiamata al metodo ambigua.

  • Il requisito generale per la compatibilità all'indietro implica che Sun dovrebbe implementarlo come nuovi metodi. Non possono modificare i comportamenti dei metodi attuali perché ciò potrebbe potenzialmente rompere migliaia di applicazioni esistenti dei clienti.

C'è un ultimo punto che si riferisce specificamente al caso d'uso dell'OP (come descritto). L'OP dice che il suo codice non sa se prevedere (ad esempio) un metodo con un parametro int o Integer nella classe di destinazione. Supponiamo che la persona che ha scritto la classe target abbia fornito entrambi gli overload ... e la semantica sia sottilmente (o non sottotitolata) diversa? Qualunque cosa tu faccia, ci saranno situazioni in cui il codice dell'OP preleva il sovraccarico che il cliente non si aspetta. Cattivo.

IMO, è meglio per il codice dell'OP imporre alcune semplici regole API che dicono quando è corretto utilizzare le primitive contro i wrapper.

+0

Grazie, pensandoci, questo ha davvero senso. Forse dovrei semplicemente fare la regola che le classi riflesse possono contenere solo tipi avvolti se vogliono essere chiamati. – Daff

0

È possibile aggiungere ulteriore logica alla chiamata di riflessione che tenta di convertire il Integer.class (o qualsiasi altro) nella classe primitiva corrispondente, quindi cercare ripetutamente il metodo finché non si ottiene una corrispondenza. Se hai Apache Commons Lang, allora il metodo wrapperToPrimitive farà questa conversazione per te, ma la scrittura da te è banale.

  1. Eseguire getMethod come al solito
  2. Se non abbiamo trovato nulla, quindi cercare eventuali tipi di parametri che hanno primitive corrispondenti
  3. Per ogni combinazione di questi, eseguire un'altra ricerca fino a quando qualcosa bastoni.

Non elegante, e per i metodi con molti parametri primitivi, potrebbe anche essere lento.

9

Se si dispone di commons-lang con versione> = 2.5, è possibile utilizzare MethodUtils.getMatchingAccessibleMethod (...) in grado di gestire i problemi dei tipi di boxe.

+0

Funziona come un incantesimo. – rlegendi

0

Provare a utilizzare Class.isPrimitive() per determinare se è un tipo primitivo, quindi se lo è, utilizzare le riflessioni per recuperare il campo TYPE e verificare se è uguale. Quindi, in pseudocodice molto liberale:

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

In altre notizie, sono dislessico - per anni (fino a doverlo digitare a mano sopra) ho pensato che fosse 'isAssignableForm() ' –