Kann ich den Methodenparameternamen mithilfe von Java Reflection abrufen?

124

Wenn ich eine Klasse wie diese habe:

public class Whatever
{
  public void aMethod(int aParam);
}

Gibt es eine Möglichkeit zu wissen, dass aMethodein Parameter namens verwendet aParamwird, der vom Typ ist int?

Geo
quelle
10
7 Antworten und niemand erwähnte den Begriff "Methodensignatur". Dies ist im Grunde das, was Sie mit Reflexion überprüfen können, was bedeutet: keine Parameternamen.
Ewernli
3
Es ist möglich, siehe meine Antwort unten
Flybywire
4
6 Jahre später ist es jetzt über Reflexion in Java 8 möglich , siehe diese SO-Antwort
earcam

Antworten:

90

Zusammenfassen:

  • Das Abrufen von Parameternamen ist möglich, wenn Debug-Informationen während der Kompilierung enthalten sind. Siehe diese Antwort für weitere Details
  • Andernfalls ist es nicht möglich , Parameternamen abzurufen
  • Das Abrufen des Parametertyps ist mit möglich method.getParameterTypes()

Um die Autovervollständigungsfunktion für einen Editor zu schreiben (wie Sie in einem der Kommentare angegeben haben), gibt es einige Optionen:

  • Verwendung arg0, arg1, arg2usw.
  • Verwendung intParam, stringParam, objectTypeParametc.
  • Verwenden Sie eine Kombination der oben genannten - die erstere für nicht-primitive Typen und die letztere für primitive Typen.
  • Zeige überhaupt keine Argumentnamen an - nur die Typen.
Bozho
quelle
4
Ist das mit Interfaces möglich? Ich konnte immer noch keinen Weg finden, um Namen von Schnittstellenparametern zu erhalten.
Cemo
@Cemo: Konnten Sie einen Weg finden, um Namen von Schnittstellenparametern zu erhalten?
Vaibhav Gupta
Aus diesem Grund benötigt spring die Annotation @ConstructorProperties, um Objekte aus primitiven Typen eindeutig zu erstellen.
Bharat
102

In Java 8 können Sie Folgendes tun:

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.List;

public final class Methods {

    public static List<String> getParameterNames(Method method) {
        Parameter[] parameters = method.getParameters();
        List<String> parameterNames = new ArrayList<>();

        for (Parameter parameter : parameters) {
            if(!parameter.isNamePresent()) {
                throw new IllegalArgumentException("Parameter names are not present!");
            }

            String parameterName = parameter.getName();
            parameterNames.add(parameterName);
        }

        return parameterNames;
    }

    private Methods(){}
}

Für Ihre Klasse können Whateverwir also einen manuellen Test durchführen:

import java.lang.reflect.Method;

public class ManualTest {
    public static void main(String[] args) {
        Method[] declaredMethods = Whatever.class.getDeclaredMethods();

        for (Method declaredMethod : declaredMethods) {
            if (declaredMethod.getName().equals("aMethod")) {
                System.out.println(Methods.getParameterNames(declaredMethod));
                break;
            }
        }
    }
}

Dies sollte gedruckt werden, [aParam]wenn Sie ein -parametersArgument an Ihren Java 8-Compiler übergeben haben.

Für Maven-Benutzer:

<properties>
    <!-- PLUGIN VERSIONS -->
    <maven-compiler-plugin.version>3.1</maven-compiler-plugin.version>

    <!-- OTHER PROPERTIES -->
    <java.version>1.8</java.version>
</properties>

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>${maven-compiler-plugin.version}</version>
            <configuration>
                <!-- Original answer -->
                <compilerArgument>-parameters</compilerArgument>
                <!-- Or, if you use the plugin version >= 3.6.2 -->
                <parameters>true</parameters>
                <testCompilerArgument>-parameters</testCompilerArgument>
                <source>${java.version}</source>
                <target>${java.version}</target>
            </configuration>
        </plugin>
    </plugins>
</build>

Weitere Informationen finden Sie unter folgenden Links:

  1. Offizielles Java-Tutorial: Abrufen von Namen von Methodenparametern
  2. JEP 118: Zugriff auf Parameternamen zur Laufzeit
  3. Javadoc für Parameterklasse
lpandzic
quelle
2
Ich weiß nicht, ob sie die Argumente für das Compiler-Plugin geändert haben, aber mit der neuesten Version (ab sofort 3.5.1) musste ich Compiler-Argumente im Konfigurationsabschnitt verwenden: <configuration> <compilerArgs> <arg> - Parameter </ arg> </ compilerArgs> </ configuration>
max
15

Die Paranamer-Bibliothek wurde erstellt, um dasselbe Problem zu lösen.

Es wird versucht, Methodennamen auf verschiedene Arten zu ermitteln. Wenn die Klasse mit Debugging kompiliert wurde, kann sie die Informationen durch Lesen des Bytecodes der Klasse extrahieren.

Eine andere Möglichkeit besteht darin, ein privates statisches Element in den Bytecode der Klasse einzufügen, nachdem es kompiliert wurde, aber bevor es in ein Glas gestellt wird. Anschließend werden diese Informationen mithilfe von Reflection zur Laufzeit aus der Klasse extrahiert.

https://github.com/paul-hammant/paranamer

Ich hatte Probleme mit dieser Bibliothek, aber am Ende habe ich sie zum Laufen gebracht. Ich hoffe, die Probleme dem Betreuer melden zu können.

Sarel Botha
quelle
Ich versuche, Paranamer in einem Android-Apk zu verwenden. Aber ich ParameterNAmesNotFoundException
bekomme
10

Siehe org.springframework.core.DefaultParameterNameDiscoverer-Klasse

DefaultParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer();
String[] params = discoverer.getParameterNames(MathUtils.class.getMethod("isPrime", Integer.class));
Denis Danilkovich
quelle
10

Ja.
Der Code muss mit einem Java 8-kompatiblen Compiler kompiliert werden, mit der Option zum Speichern der aktivierten formalen Parameternamen ( Option -parameters ).
Dann sollte dieses Code-Snippet funktionieren:

Class<String> clz = String.class;
for (Method m : clz.getDeclaredMethods()) {
   System.err.println(m.getName());
   for (Parameter p : m.getParameters()) {
    System.err.println("  " + p.getName());
   }
}
Karol Król
quelle
Versuchte dies und es hat funktioniert! Ein Tipp: Ich musste Ihr Projekt neu erstellen, damit diese Compiler-Konfigurationen wirksam werden.
ishmaelMakitla
5

Sie können die Methode mit Reflektion abrufen und ihre Argumenttypen erkennen. Überprüfen Sie http://java.sun.com/j2se/1.4.2/docs/api/java/lang/reflect/Method.html#getParameterTypes%28%29

Sie können jedoch den Namen des verwendeten Arguments nicht angeben.

Johnco
quelle
17
Das ist wirklich alles möglich. Seine Frage betraf jedoch ausdrücklich den Parameternamen . Überprüfen Sie den Topiktitel.
BalusC
1
Und ich zitiere: "Sie können jedoch den Namen des verwendeten Arguments nicht sagen." Lies einfach meine Antwort -_-
Johnco
3
Die Frage war nicht so formuliert, dass er nicht wusste, wie man den Typ erhält.
BalusC
3

Es ist möglich und Spring MVC 3 macht es, aber ich habe mir nicht die Zeit genommen, genau zu sehen, wie.

Der Abgleich von Methodenparameternamen mit URI-Vorlagenvariablennamen ist nur möglich, wenn Ihr Code mit aktiviertem Debugging kompiliert wurde. Wenn Sie das Debuggen nicht aktiviert haben, müssen Sie den Namen des Variablennamens der URI-Vorlage in der Annotation @PathVariable angeben, um den aufgelösten Wert des Variablennamens an einen Methodenparameter zu binden. Beispielsweise:

Entnommen aus der Federdokumentation

Flybywire
quelle
org.springframework.core.Conventions.getVariableName (), aber ich nehme an, dass Sie cglib als Abhängigkeit haben müssen
Radu Toader
3

Während es nicht möglich ist (wie andere gezeigt haben), Sie könnten eine Anmerkung verwenden , um die Parameter - Namen zu übertragen, und dass , obwohl Reflexion erhalten.

Nicht die sauberste Lösung, aber sie erledigt den Job. Einige Webservices tun dies tatsächlich, um Parameternamen beizubehalten (dh: Bereitstellen von WSs mit Glassfish).

WhyNotHugo
quelle
3

Sie sollten also in der Lage sein:

Whatever.declaredMethods
        .find { it.name == 'aMethod' }
        .parameters
        .collect { "$it.type : $it.name" }

Aber Sie werden wahrscheinlich eine Liste wie diese erhalten:

["int : arg0"]

Ich glaube, dass dies in Groovy 2.5+ behoben wird

Derzeit lautet die Antwort also:

  • Wenn es eine Groovy-Klasse ist, dann nein, Sie können den Namen nicht bekommen, aber Sie sollten es in Zukunft können.
  • Wenn es sich um eine Java-Klasse handelt, die unter Java 8 kompiliert wurde, sollten Sie dazu in der Lage sein.

Siehe auch:


Für jede Methode dann so etwas wie:

Whatever.declaredMethods
        .findAll { !it.synthetic }
        .collect { method -> 
            println method
            method.name + " -> " + method.parameters.collect { "[$it.type : $it.name]" }.join(';')
        }
        .each {
            println it
        }
tim_yates
quelle
Ich möchte auch nicht den Namen einer Methode angeben aMethod. Ich möchte es für alle Methoden in einer Klasse bekommen.
Snoop
@ Snoop fügte ein Beispiel hinzu, wie man jede nicht synthetische Methode
erhält
Können wir nicht verwenden antlr, um Parameternamen dafür zu erhalten?
Snoop
2

Wenn Sie die Eclipse verwenden, sehen Sie sich das folgende Bild an, damit der Compiler die Informationen zu Methodenparametern speichern kann

Geben Sie hier die Bildbeschreibung ein

Hazim
quelle
2

Wie @Bozho sagte, ist dies möglich, wenn Debug-Informationen während der Kompilierung enthalten sind. Hier gibt es eine gute Antwort ...

Wie erhalte ich die Parameternamen der Konstruktoren eines Objekts (Reflektion)? von @AdamPaynter

... mit der ASM-Bibliothek. Ich habe ein Beispiel zusammengestellt, das zeigt, wie Sie Ihr Ziel erreichen können.

Beginnen Sie zunächst mit einer pom.xml mit diesen Abhängigkeiten.

<dependency>
    <groupId>org.ow2.asm</groupId>
    <artifactId>asm-all</artifactId>
    <version>5.2</version>
</dependency>
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
</dependency>

Dann sollte diese Klasse tun, was Sie wollen. Rufen Sie einfach die statische Methode auf getParameterNames().

import org.objectweb.asm.ClassReader;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.LocalVariableNode;
import org.objectweb.asm.tree.MethodNode;

public class ArgumentReflection {
    /**
     * Returns a list containing one parameter name for each argument accepted
     * by the given constructor. If the class was compiled with debugging
     * symbols, the parameter names will match those provided in the Java source
     * code. Otherwise, a generic "arg" parameter name is generated ("arg0" for
     * the first argument, "arg1" for the second...).
     * 
     * This method relies on the constructor's class loader to locate the
     * bytecode resource that defined its class.
     * 
     * @param theMethod
     * @return
     * @throws IOException
     */
    public static List<String> getParameterNames(Method theMethod) throws IOException {
        Class<?> declaringClass = theMethod.getDeclaringClass();
        ClassLoader declaringClassLoader = declaringClass.getClassLoader();

        Type declaringType = Type.getType(declaringClass);
        String constructorDescriptor = Type.getMethodDescriptor(theMethod);
        String url = declaringType.getInternalName() + ".class";

        InputStream classFileInputStream = declaringClassLoader.getResourceAsStream(url);
        if (classFileInputStream == null) {
            throw new IllegalArgumentException(
                    "The constructor's class loader cannot find the bytecode that defined the constructor's class (URL: "
                            + url + ")");
        }

        ClassNode classNode;
        try {
            classNode = new ClassNode();
            ClassReader classReader = new ClassReader(classFileInputStream);
            classReader.accept(classNode, 0);
        } finally {
            classFileInputStream.close();
        }

        @SuppressWarnings("unchecked")
        List<MethodNode> methods = classNode.methods;
        for (MethodNode method : methods) {
            if (method.name.equals(theMethod.getName()) && method.desc.equals(constructorDescriptor)) {
                Type[] argumentTypes = Type.getArgumentTypes(method.desc);
                List<String> parameterNames = new ArrayList<String>(argumentTypes.length);

                @SuppressWarnings("unchecked")
                List<LocalVariableNode> localVariables = method.localVariables;
                for (int i = 1; i <= argumentTypes.length; i++) {
                    // The first local variable actually represents the "this"
                    // object if the method is not static!
                    parameterNames.add(localVariables.get(i).name);
                }

                return parameterNames;
            }
        }

        return null;
    }
}

Hier ist ein Beispiel mit einem Unit-Test.

public class ArgumentReflectionTest {

    @Test
    public void shouldExtractTheNamesOfTheParameters3() throws NoSuchMethodException, SecurityException, IOException {

        List<String> parameterNames = ArgumentReflection
                .getParameterNames(Clazz.class.getMethod("callMe", String.class, String.class));
        assertEquals("firstName", parameterNames.get(0));
        assertEquals("lastName", parameterNames.get(1));
        assertEquals(2, parameterNames.size());

    }

    public static final class Clazz {

        public void callMe(String firstName, String lastName) {
        }

    }
}

Das vollständige Beispiel finden Sie auf GitHub

Vorsichtsmaßnahmen

  • Ich habe die ursprüngliche Lösung von @AdamPaynter leicht geändert, damit sie für Methoden funktioniert. Wenn ich es richtig verstanden habe, funktioniert seine Lösung nur mit Konstruktoren.
  • Diese Lösung funktioniert nicht mit staticMethoden. Dies liegt daran, dass in diesem Fall die Anzahl der von ASM zurückgegebenen Argumente unterschiedlich ist, dies jedoch leicht behoben werden kann.
danidemi
quelle
0

Parameternamen sind nur für den Compiler nützlich. Wenn der Compiler eine Klassendatei generiert, werden die Parameternamen nicht berücksichtigt. Die Argumentliste einer Methode besteht nur aus der Anzahl und den Typen ihrer Argumente. Es ist also unmöglich, den Parameternamen mithilfe von Reflection (wie in Ihrer Frage markiert) abzurufen - er existiert nirgendwo.

Wenn die Verwendung von Reflektion jedoch keine schwierige Anforderung ist, können Sie diese Informationen direkt aus dem Quellcode abrufen (vorausgesetzt, Sie haben sie).

danben
quelle
2
Parameternamen sind nicht nur für einen Compiler nützlich, sondern vermitteln auch Informationen an den Konsumenten einer Bibliothek. Nehmen wir an, ich habe eine Klasse StrangeDate geschrieben, die die Methode add (int Tag, int Stunde, int Minute) hatte, wenn Sie diese verwendet haben und eine Methode gefunden haben add (int arg0, int arg1, int arg2) wie nützlich wäre das?
David Waters
Es tut mir leid - Sie haben meine Antwort völlig falsch verstanden. Bitte lesen Sie es im Zusammenhang mit der Frage erneut.
Danben
2
Das Erfassen der Namen von Parametern ist ein großer Vorteil, wenn diese Methode reflektiert aufgerufen wird. Es ist nicht nur für den Compiler nützlich, auch im Kontext der Frage.
CorsiKa
Parameter names are only useful to the compiler.Falsch. Schauen Sie sich die Nachrüstbibliothek an. Es verwendet dynamische Schnittstellen, um REST-API-Anforderungen zu erstellen. Eine seiner Funktionen ist die Möglichkeit, Platzhalternamen in den URL-Pfaden zu definieren und diese Platzhalter durch die entsprechenden Parameternamen zu ersetzen.
TheRealChx101
0

Um meine 2 Cent hinzuzufügen; Parameterinformationen sind in einer Klassendatei "zum Debuggen" verfügbar, wenn Sie javac -g zum Kompilieren der Quelle verwenden. Und es steht APT zur Verfügung, aber Sie benötigen eine Anmerkung, die für Sie keinen Nutzen hat. (Jemand hat vor 4-5 Jahren hier etwas Ähnliches besprochen: http://forums.java.net/jive/thread.jspa?messageID=13467&tstart=0 )

Kurz gesagt, Sie können es nur erhalten, wenn Sie direkt an Quelldateien arbeiten (ähnlich wie bei APT zur Kompilierungszeit).

Elister
quelle