Posso ottenere il nome del parametro del metodo utilizzando Java reflection?


124

Se ho una classe come questa:

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

c'è un modo per sapere che aMethodutilizza un parametro denominato aParam, che è di tipo int?


10
7 risposte e nessuno ha menzionato il termine "firma del metodo". Questo è fondamentalmente ciò che puoi esaminare con la riflessione, il che significa: nessun nome di parametro.
ewernli

3
esso è possibile, vedere la mia risposta qui sotto
fly-by-wire

4
6 anni dopo, ora è possibile tramite la riflessione in Java 8 , vedere questa risposta SO
earcam

Risposte:


90

Riassumere:

  • ottenere i nomi dei parametri è possibile se le informazioni di debug sono incluse durante la compilazione. Vedi questa risposta per maggiori dettagli
  • altrimenti non è possibile ottenere i nomi dei parametri
  • ottenere il tipo di parametro è possibile, utilizzando method.getParameterTypes()

Per il bene di scrivere funzionalità di completamento automatico per un editor (come hai affermato in uno dei commenti) ci sono alcune opzioni:

  • uso arg0, arg1, arg2etc.
  • uso intParam, stringParam, objectTypeParam, etc.
  • utilizzare una combinazione di quanto sopra: il primo per i tipi non primitivi e il secondo per i tipi primitivi.
  • non mostrare affatto i nomi degli argomenti - solo i tipi.

4
È possibile con le interfacce? Non riuscivo ancora a trovare un modo per ottenere i nomi dei parametri dell'interfaccia.
Cemo

@Cemo: sei riuscito a trovare un modo per ottenere i nomi dei parametri dell'interfaccia?
Vaibhav Gupta

Solo per aggiungere, ecco perché la primavera ha bisogno dell'annotazione @ConstructorProperties per creare oggetti in modo non ambiguo da tipi primitivi.
Bharat

102

In Java 8 puoi fare quanto segue:

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(){}
}

Quindi per la tua classe Whateverpossiamo fare un test manuale:

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;
            }
        }
    }
}

che dovrebbe essere stampato [aParam]se hai passato un -parametersargomento al tuo compilatore Java 8.

Per gli utenti Maven:

<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>

Per ulteriori informazioni vedere i seguenti link:

  1. Tutorial Java ufficiale: ottenere i nomi dei parametri dei metodi
  2. JEP 118: accesso ai nomi dei parametri in fase di esecuzione
  3. Javadoc per la classe Parameter

2
Non so se hanno cambiato gli argomenti per il plugin del compilatore, ma con l'ultimo (3.5.1 per ora) ho dovuto usare gli argomenti del compilatore nella sezione di configurazione: <configuration> <compilerArgs> <arg> - parametri </arg> </compilerArgs> </configuration>
massimo

15

La libreria Paranamer è stata creata per risolvere questo stesso problema.

Prova a determinare i nomi dei metodi in diversi modi. Se la classe è stata compilata con il debugging può estrarre le informazioni leggendo il bytecode della classe.

Un altro modo è che inserisca un membro statico privato nel bytecode della classe dopo che è stato compilato, ma prima che venga inserito in un jar. Quindi utilizza la riflessione per estrarre queste informazioni dalla classe in fase di esecuzione.

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

Ho avuto problemi con questa libreria, ma alla fine l'ho fatta funzionare. Spero di segnalare i problemi al manutentore.


Sto cercando di utilizzare Paranamer all'interno di un apk Android. Ma sto ottenendoParameterNAmesNotFoundException
Rilwan

10

vedere la classe org.springframework.core.DefaultParameterNameDiscoverer

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

10

Sì.
Il codice deve essere compilato con un compilatore conforme a Java 8 con l'opzione per memorizzare i nomi dei parametri formali attivata ( opzione -parameters ).
Quindi questo snippet di codice dovrebbe funzionare:

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

Ho provato e ha funzionato! Un suggerimento però, ho dovuto ricostruire il tuo progetto affinché queste configurazioni del compilatore abbiano effetto.
ishmaelMakitla

5

Puoi recuperare il metodo con la reflection e rilevare i suoi tipi di argomento. Controlla http://java.sun.com/j2se/1.4.2/docs/api/java/lang/reflect/Method.html#getParameterTypes%28%29

Tuttavia, non puoi dire il nome dell'argomento utilizzato.


17
Davvero è tutto possibile. La sua domanda era tuttavia esplicitamente sul nome del parametro . Controlla il titolo in alto.
BalusC

1
E cito: "Tuttavia, non puoi dire il nome dell'argomento usato". basta leggere la mia risposta -_-
Johnco

3
La domanda non è stata formulata in quel modo che non sapeva come ottenere il tipo.
BalusC

3

È possibile e Spring MVC 3 lo fa, ma non mi sono preso il tempo per vedere esattamente come.

La corrispondenza dei nomi dei parametri del metodo con i nomi delle variabili del modello URI può essere eseguita solo se il codice è compilato con il debug abilitato. Se il debug non è abilitato, è necessario specificare il nome della variabile del modello URI nell'annotazione @PathVariable per associare il valore risolto del nome della variabile a un parametro del metodo. Per esempio:

Tratto dalla documentazione primaverile


org.springframework.core.Conventions.getVariableName () ma suppongo che tu debba avere cglib come dipendenza
Radu Toader

3

Sebbene non sia possibile (come altri hanno illustrato), è possibile utilizzare un'annotazione per trasferire il nome del parametro e ottenerlo tramite riflessione.

Non è la soluzione più pulita, ma porta a termine il lavoro. Alcuni servizi web in realtà lo fanno per mantenere i nomi dei parametri (es .: distribuire WS con glassfish).



3

Quindi dovresti essere in grado di fare:

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

Ma probabilmente otterrai una lista in questo modo:

["int : arg0"]

Credo che questo verrà risolto in Groovy 2.5+

Quindi, attualmente, la risposta è:

  • Se è una classe Groovy, allora no, non puoi ottenere il nome, ma dovresti essere in grado di farlo in futuro.
  • Se è una classe Java compilata sotto Java 8, dovresti essere in grado di farlo.

Guarda anche:


Per ogni metodo, quindi qualcosa come:

Whatever.declaredMethods
        .findAll { !it.synthetic }
        .collect { method -> 
            println method
            method.name + " -> " + method.parameters.collect { "[$it.type : $it.name]" }.join(';')
        }
        .each {
            println it
        }

Inoltre non voglio specificare il nome di un metodo aMethod. Voglio ottenerlo per tutti i metodi in una classe.
snoop

@snoop ha aggiunto un esempio per ottenere tutti i metodi non sintetici
tim_yates

Non possiamo usare antlrper ottenere i nomi dei parametri per questo?
snoop

2

se usi l'eclissi, guarda l'immagine qui sotto per consentire al compilatore di memorizzare le informazioni sui parametri del metodo

inserisci qui la descrizione dell'immagine


2

Come affermato da @Bozho, è possibile farlo se le informazioni di debug sono incluse durante la compilazione. C'è una buona risposta qui ...

Come ottenere i nomi dei parametri dei costruttori di un oggetto (riflessione)? di @AdamPaynter

... utilizzando la libreria ASM. Ho messo insieme un esempio che mostra come puoi raggiungere il tuo obiettivo.

Prima di tutto, inizia con un pom.xml con queste dipendenze.

<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>

Quindi, questa classe dovrebbe fare quello che vuoi. Basta invocare il metodo statico 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;
    }
}

Ecco un esempio con uno 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) {
        }

    }
}

Puoi trovare l'esempio completo su GitHub

Avvertenze

  • Ho leggermente modificato la soluzione originale da @AdamPaynter per farlo funzionare per Methods. Se ho capito bene, la sua soluzione funziona solo con i costruttori.
  • Questa soluzione non funziona con i staticmetodi. Questo perché in questo caso il numero di argomenti restituiti da ASM è diverso, ma è qualcosa che può essere facilmente risolto.

0

I nomi dei parametri sono utili solo per il compilatore. Quando il compilatore genera un file di classe, i nomi dei parametri non vengono inclusi: l'elenco degli argomenti di un metodo consiste solo del numero e dei tipi dei suoi argomenti. Quindi sarebbe impossibile recuperare il nome del parametro usando la riflessione (come etichettato nella tua domanda) - non esiste da nessuna parte.

Tuttavia, se l'uso della reflection non è un requisito fondamentale, puoi recuperare queste informazioni direttamente dal codice sorgente (supponendo che tu lo abbia).


2
Il nome del parametro non è solo utile a un compilatore, ma trasmette anche informazioni al consumatore di una libreria, supponiamo che io abbia scritto una classe StrangeDate che aveva il metodo add (int day, int hour, int minute) se sei andato a usarlo e hai trovato un metodo add (int arg0, int arg1, int arg2) quanto sarebbe utile?
David Waters

Mi dispiace - hai frainteso completamente la mia risposta. Si prega di rileggerlo nel contesto della domanda.
Danben

2
L'acquisizione dei nomi dei parametri è un grande vantaggio per chiamare quel metodo in modo riflessivo. Non è solo utile al compilatore, anche nel contesto della domanda.
corsiKa

Parameter names are only useful to the compiler.Sbagliato. Guarda la libreria Retrofit. Utilizza interfacce dinamiche per creare richieste API REST. Una delle sue caratteristiche è la capacità di definire i nomi dei segnaposto nei percorsi dell'URL e sostituire quei segnaposto con i nomi dei parametri corrispondenti.
TheRealChx101

0

Aggiungere i miei 2 centesimi; le informazioni sui parametri sono disponibili in un file di classe "per il debug" quando si utilizza javac -g per compilare il sorgente. Ed è disponibile per APT ma avrai bisogno di un'annotazione quindi non ti serve. (Qualcuno ha discusso qualcosa di simile 4-5 anni fa qui: http://forums.java.net/jive/thread.jspa?messageID=13467&tstart=0 )

Nel complesso, in breve, non puoi ottenerlo a meno che tu non lavori direttamente sui file Source (simile a quello che fa APT in fase di compilazione).

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.