2010-02-09 13 views
5

Come parte dello sviluppo di un piccolo ScriptEngine, richiamo in modo riflessivo i metodi java. Una chiamata dal motore di script mi ​​fornisce l'oggetto il nome del metodo e un array di argomenti. Per chiamare il metodo ho provato a risolverlo con una chiamata a Class.getMethod (nome, tipi di argomenti).
Ciò tuttavia funziona solo quando le classi degli argomenti e le classi previste dal metodo sono uguali.Chiamare il metodo di adattamento più vicino

Object o1 = new Object(); 
Object out = System.out; 
//Works as System.out.println(Object) is defined 
Method ms = out.getClass().getMethod("println",o1.getClass()); 
Object o2 = new Integer(4); 
//Does not work as System.out.println(Integer) is not defined 
Method mo = out.getClass().getMethod("println",o2.getClass()); 

Vorrei sapere se c'è un modo "semplice" per ottenere il giusto metodo, se possibile con la misura più vicina per i tipi di argomento, o se devo implementare questa me stesso.

in forma più vicina potrebbe essere:

Object o1 = new Integer(1); 
Object o2 = new String(""); 
getMethod(name, o1.getClass())//println(Object) 
getMethod(name, o2.getClass())//println(String) 

Aggiornamento:
Per chiarire che cosa ho bisogno: il motore di script è un piccolo progetto che scrivo nel mio tempo libero in modo non ci sono regole STRIKT ho da seguire. Quindi ho pensato che selezionando i metodi chiamati dal motore nello stesso modo in cui il compilatore java seleziona i metodi in fase di compilazione solo con il tipo dinamico e non il tipo statico dell'oggetto funzionerebbe. (Con o senza autoboxing)
Questo è quello che prima speravo che il Class.getMethod() avrebbe risolto. Ma Class.getMethod() richiede esattamente le stesse Classi dei tipi di argomenti che il metodo dichiara, usando una sottoclasse si otterrà un tale metodo Eccezione. Ciò può accadere per buone ragioni, ma rende il metodo inutile per me, poiché non conosco in anticipo i tipi di argomenti che si adatterebbero.
Un'alternativa sarebbe chiamare Class.getMethods() e scorrere l'array restituito e provare a trovare un metodo di adattamento. Ciò tuttavia essere complicato se non voglio solo fare il primo metodo di "buono", che mi imbatto in, così ho sperato che ci sarebbe una soluzione esistente, che almeno maniglie:

  • fit più vicino: IF arg.getClass() == sottoclassi e metodi m (Superclasse), m (sottoclasse) quindi chiamare m (sottoclasse)
  • argomenti variabili: System.out.printf (String, String ...)

Anche il supporto per l'autoboxing sarebbe bello.
Se una chiamata non può essere risolta, può generare un'eccezione (ma (String, Object), ma (Object, String), args = String, String)
(Se l'hai fatto fino a qui, grazie per aver dedicato del tempo a leggerlo :-))

+0

prima definire regole severe per "più vicino", quindi implementazione è un dettaglio. – Bozho

+0

proprio quello che mi stavo chiedendo anch'io. Ma per tutti i risponditori pedanticamente corretti che dicono "Definisci il più vicino", ne ho uno semplice: quello che Java normalmente sceglierebbe (credo a livello di classe). –

risposta

2

Come altri hanno sottolineato, non esiste un metodo standard che faccia questo, quindi dovrai implementare il tuo algoritmo di risoluzione del sovraccarico.

Sarebbe probabilmente un senso di seguire più fedelmente le regole di risoluzione del sovraccaricamento di javac possibile:
http://java.sun.com/docs/books/jls/third_edition/html/expressions.html#292575
Probabilmente si può ignorare farmaci generici per un linguaggio di scripting dinamico tipizzato, ma si potrebbe ancora beneficiare della bridge methods che il compilatore genera automaticamente.

alcune insidie ​​a cui fare attenzione:

  • Class.isAssignableFrom non conosce allargando conversioni automatiche primitive, perché questi sono zucchero sintattico implementato nel compilatore; Non si verificano nella VM o nella gerarchia di classi. per esempio. int.class.isAssignableFrom(short.class) restituisce false.
  • Analogamente, Class.isAssignableFrom non conosce il box automatico. Integer.class.isAssignableFrom(int.class) restituisce false.
  • Class.isInstance e Class.cast prendere un argomento Object; Non puoi passare a valori primitivi. Tornano anche una Object, in modo che non possono essere utilizzati per unboxing ((int) new Integer(42) è legale nella sorgente di Java, ma int.class.cast(new Integer(42)) genera un'eccezione.)
1

AFAIK, non esiste un modo semplice per fare questo genere di cose. Certamente, non c'è nulla nelle librerie di classi Java standard per farlo.

Il problema è che non esiste una singola risposta "giusta". È necessario considerare tutti i casi d'uso, decidere quale dovrebbe essere il "metodo giusto" e implementare di conseguenza il codice di riflessione.

2

Suggerisco di utilizzare getMethods(). Restituisce una matrice di tutti i metodi pubblici (Method[]).

La cosa più importante è:
" Se la classe dichiara più metodi utente pubbliche con gli stessi tipi di parametri, sono tutti inclusi nella matrice restituita. "

Cosa sarà quindi necessario fare è quello di utilizzare i risultati in questo array per stabilire che uno di loro (se presente) sono la corrispondenza più vicina. Dato che la corrispondenza più vicina dovrebbe dipendere molto dalle tue esigenze e dall'applicazione specifica, ha senso codificarla tu stesso.


codice di esempio che illustra un metodo di come si potrebbe andare a fare questo:

public Method getMethod(String methodName, Class<?> clasz) 
{ 
    try 
    { 
     Method[] methods = clasz.getMethods(); 
     for (Method method : methods) 
     { 
      if (methodName.equals(method.getName())) 
      { 
       Class<?>[] params = method.getParameterTypes(); 
       if (params.length == 1) 
       { 
        Class<?> param = params[0]; 
        if ((param == int.class) || (param == float.class) || (param == float.class)) 
        { 
         //method.invoke(object, value); 
         return method; 
        } 
        else if (param.isAssignableFrom(Number.class)) 
        { 
         return method; 
        } 
        //else if (...) 
        //{ 
        // ... 
        //} 
       } 
      } 
     } 
    } 
    catch (Exception e) 
    { 
     //some handling 
    } 
    return null; 
} 

In questo esempio, il metodo getMethod(String, Class<?>) restituirà un metodo che con un solo parametro che è un int, float , double o una superclasse di Number.

Questa è una implementazione rudimentale - Restituisce il primo metodo che si adatta al progetto. Dovresti estenderlo per creare un elenco di tutti i metodi che corrispondono, quindi ordinarli secondo alcuni criteri e restituire il metodo di corrispondenza migliore.

È quindi possibile prendere ancora di più creando il metodo più generale getMethod(String, Class<?>), per gestire più dei possibili scenari di "stretta corrispondenza", e forse anche più di una paramter

HTH


Modifica: Come sottolineato da @finnw, fare attenzione quando si utilizza Class#isAssignableFrom(Class<?> cls), a causa delle sue limitazioni, come ho nel mio codice di esempio, testare le primitive separatamente dagli oggetti Number.

+1

@ bguiz, bella soluzione. Voglio solo aggiungere che è possibile testare 'Class.isPrimitive()' e 'Class.isArray()' per aggirare alcune delle limitazioni di isAssignableFrom(). @ Anche la risposta di Finnw. – bojangle