2010-02-09 9 views
5

Im Rahmen der Entwicklung einer kleinen ScriptEngine benenne ich nachdenklich Java-Methoden. Ein Aufruf durch die Skript-Engine gibt mir das Objekt den Methodennamen und ein Array von Argumenten. Um die Methode aufzurufen, habe ich versucht, sie mit einem Aufruf von Class.getMethod (Name, Argumenttypen) aufzulösen.
Dies funktioniert jedoch nur, wenn die Klassen der Argumente und die von der Methode erwarteten Klassen identisch sind.Aufruf der engsten Anpassungsmethode

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()); 

Ich möchte wissen, ob es eine „einfache“ Art und Weise ist die richtige Methode zu erhalten, wenn möglich mit dem engsten fit für die Argumenttypen, oder wenn ich das selbst habe implementieren.

Closest fit wäre:

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

Update:
Um zu klären, was ich brauche: Das Script Engine ist ein kleines Projekt, das ich in meiner Freizeit schreiben, so dass keine strikt Regeln ich habe, sind Folgen. Also dachte ich, dass das Auswählen von Methoden, die von der Engine aufgerufen werden, genauso wie der Java-Compiler Methoden zur Kompilierzeit auswählt, nur mit dem dynamischen Typ und nicht mit dem statischen Typ des Objekts (mit oder ohne Autobox)
Dies ist, was ich zuerst hoffte, dass die Class.getMethod() lösen würde. Aber die Class.getMethod() benötigt genau die gleichen Klassen wie Argumenttypen, wie die Methode deklariert, und die Verwendung einer Unterklasse führt zu keiner solchen Methode Exception. Dies mag aus guten Gründen geschehen, macht aber die Methode für mich unbrauchbar, da ich nicht im Voraus weiß, welche Argumenttypen passen würden.
Eine Alternative wäre, Class.getMethods() aufzurufen und das zurückgegebene Array zu durchlaufen und zu versuchen, eine passende Methode zu finden. Dies würde jedoch kompliziert sein, wenn ich einfach nicht die erste „gute“ Methode nehmen will, die ich gestoßen, so hoffte ich, dass es eine bestehende Lösung wäre, die zumindest Griffe:

  • am nächsten Sitz: Wenn arg.getClass() == Subklasse und Methoden m (Oberklasse), m (Subklasse), dann rufen m (Subklasse)
  • variable Argumente: System.out.printf (String, String ...)

Unterstützung für Autoboxing wäre auch nett.
Wenn ein Aufruf nicht aufgelöst werden kann, kann er eine Ausnahme auslösen (ma (String, Object), ma (Objekt, String), args = String, String)
(Wenn Sie es bis hierher geschafft haben, danke, dass Sie sich die Zeit genommen haben lese es :-))

+0

definieren erste strenge Regeln für die „nächste“, dann ist die Umsetzung ein Detail. – Bozho

+0

genau das, worüber ich mich auch wunderte. Aber für alle pedantisch korrekten Antworter, die sagen: "Definiere am nächsten", habe ich einen einfachen: den, den Java normalerweise wählen würde (ich denke beim Klassenladen). –

Antwort

2

Wie andere darauf hingewiesen haben, gibt es keine Standardmethode, die dies tut, also müssen Sie Ihren eigenen Überladungsauflösungsalgorithmus implementieren.

wäre es wahrscheinlich sinnvoll, javac die Überladungsauflösung Regeln so eng wie möglich zu folgen:
http://java.sun.com/docs/books/jls/third_edition/html/expressions.html#292575
Sie können sich wahrscheinlich ignorieren Generika für eine dynamisch typisierte Skriptsprache, aber Sie könnten nach wie vor von der bridge methods profitieren, dass der Compiler generiert automatisch.

Einige Gefahren zu achten gilt:

  • Class.isAssignableFrom weiß nicht, über die automatische Erweiterung primitive Umwandlungen, da diese im Compiler implementiert syntaktischer Zucker sind; Sie treten nicht in der VM oder Klassenhierarchie auf. z.B. int.class.isAssignableFrom(short.class) gibt false zurück.
  • Ähnlich Class.isAssignableFrom weiß nicht über Auto-Boxen. Integer.class.isAssignableFrom(int.class) gibt false zurück.
  • Class.isInstance und Class.cast nehmen Sie eine Object als ein Argument; Sie können keine primitiven Werte an sie übergeben. Sie spielen auch eine Object zurückkehren, so dass sie nicht für Unboxing verwendet werden ((int) new Integer(42) in Java Quelle legal ist aber int.class.cast(new Integer(42)) löst eine Ausnahme.)
1

AFAIK, es gibt keine einfache Möglichkeit, so etwas zu tun. Natürlich gibt es dafür in den Standard-Java-Klassenbibliotheken nichts.

Das Problem ist, dass es keine einzige "richtige" Antwort gibt. Sie müssen alle Ihre Anwendungsfälle berücksichtigen, entscheiden, was die "richtige Methode" sein soll und Ihren Reflexionscode entsprechend implementieren.

2

Ich würde vorschlagen, dass Sie getMethods() verwenden. Es gibt ein Array aller öffentlichen Methoden zurück (Method[]).

Das Wichtigste hier ist:
" Wenn die Klasse mehr öffentlichen Member-Methoden mit den gleichen Parametertypen erklärt, sie sind alle in der zurückgegebenen Array enthalten. "

Was Sie dann tun müssen, ist zu die Ergebnisse verwenden in diesem Array zu die einer von ihnen bestimmen (falls vorhanden) sind die nächste Übereinstimmung. Da die genaue Übereinstimmung sehr stark von Ihren Anforderungen und der spezifischen Anwendung abhängt, ist es sinnvoll, sie selbst zu programmieren.


Beispielcode veranschaulicht, einen Ansatz, wie Sie dies zu tun gehen könnten:

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 diesem Beispiel werden die getMethod(String, Class<?>) Verfahren ein Verfahren zurückkehren, dass mit nur einem Parameter, der ein int ist, floatdouble oder eine Superklasse von Number.

Dies ist eine rudimentäre Implementierung - Es gibt die erste Methode zurück, die zur Rechnung passt. Sie müssten es erweitern, um eine Liste aller übereinstimmenden Methoden zu erstellen, und diese dann nach bestimmten Kriterien sortieren und die am besten passende Methode zurückgeben.

Sie können es dann sogar noch weiter durch die allgemeinere getMethod(String, Class<?>) Verfahren zu schaffen, mehr der möglichen „enge Übereinstimmung“ Szenarien zu handhaben, und möglicherweise sogar mehr als ein Paramter

HTH


Edit: Wie @finnw darauf hingewiesen hat, sei vorsichtig bei der Verwendung von Class#isAssignableFrom(Class<?> cls), aufgrund seiner Einschränkungen, wie ich in meinem Beispielcode, die Primitiven separat von den Number Objekte zu testen.

+1

@bguiz, nette Lösung. Ich möchte nur hinzufügen, dass Sie für 'Class.isPrimitive()' und 'Class.isArray()' testen können, um einige der Einschränkungen von isAssignableFrom() zu umgehen. @ Finnws Antwort auch. – bojangle