Czy Mogę uzyskać nazwę parametru metody używając Java reflection?

Jeśli mam taką klasę:

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

Czy Jest jakiś sposób, aby wiedzieć, że aMethod używa parametru o nazwie aParam, który jest typu int?

Author: Geo, 2010-02-10

14 answers

Podsumowując:

  • uzyskanie nazw parametrów jest możliwe, jeśli podczas kompilacji zawarte są informacje o debugowaniu. Zobacz ta odpowiedź Po Więcej Szczegółów
  • w przeciwnym razie uzyskanie nazw parametrów jest nie możliwe
  • uzyskanie typu parametru jest możliwe, używając method.getParameterTypes()

W celu napisania funkcji autouzupełniania dla edytora (jak napisałeś w jednym z komentarzy) jest kilka opcji:

  • użyj arg0, arg1, arg2 itd.
  • użycie intParam, stringParam, objectTypeParam, itd.
  • użyj kombinacji powyższych-pierwsza dla typów nie-prymitywnych, a druga dla typów prymitywnych.
  • w ogóle nie pokazuj nazw argumentów - tylko ich typy.
 72
Author: Bozho,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:02:39

W Javie 8 możesz wykonać następujące czynności:

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

Więc dla twojej klasy Whatever możemy zrobić test manualny:

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

Który powinien wydrukować [aParam], Jeśli przekazałeś argument -parameters do kompilatora Java 8.

Dla użytkowników 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>

Aby uzyskać więcej informacji, zobacz następujące linki:

  1. oficjalny samouczek Javy: uzyskiwanie nazw parametrów metod
  2. JEP 118: dostęp do nazw parametrów w czasie wykonywania
  3. Javadoc dla Klasa parametru
 73
Author: lpandzic,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-14 20:20:45

Biblioteka Paranamer została stworzona w celu rozwiązania tego samego problemu.

Próbuje określić nazwy metod na kilka różnych sposobów. Jeśli klasa została skompilowana z debugowaniem, może ona wyodrębnić informacje poprzez odczytanie kodu bajtowego klasy.

Innym sposobem jest wstrzyknięcie prywatnego statycznego członka do kodu bajtowego klasy po jej skompilowaniu, ale przed umieszczeniem go w jar. Następnie wykorzystuje reflection, aby wydobyć te informacje z klasy w runtime.

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

Miałem problemy z używaniem tej biblioteki, ale w końcu udało mi się ją uruchomić. Mam nadzieję zgłosić problem opiekunowi.

 13
Author: Sarel Botha,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-07-07 18:46:21

Tak.
Kod musi być skompilowany przy użyciu kompilatora zgodnego z Java 8 z włączoną opcją przechowywania formalnych nazw parametrów (opcja-parameters).
Następnie ten fragment kodu powinien działać:

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());
   }
}
 7
Author: Karol Król,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-09-02 08:41:04

Możesz pobrać metodę z odbiciem i wykryć jej typy argumentów. Sprawdź http://java.sun.com/j2se/1.4.2/docs/api/java/lang/reflect/Method.html#getParameterTypes%28%29

Nie można jednak podać nazwy użytego argumentu.

 5
Author: Johnco,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-02-10 15:13:48

Zobacz org.springframework.rdzeń.DefaultParameterNameDiscoverer class

DefaultParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer();
String[] params = discoverer.getParameterNames(MathUtils.class.getMethod("isPrime", Integer.class));
 5
Author: Denis Danilkovich,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-09-09 14:12:10

Jest to możliwe i Spring MVC 3 to robi, ale nie poświęciłem czasu, aby dokładnie zobaczyć, jak.

Dopasowanie nazw parametrów metody do nazw Zmiennych Szablonu URI można można to zrobić tylko wtedy, gdy kod jest skompilowany z włączonym debugowaniem. Jeśli masz nie włączone debugowanie, musisz podaj nazwę szablonu URI nazwa zmiennej w zmiennej @PathVariable adnotacja w celu związania rozwiązana wartość nazwy zmiennej do parametr metody. Na przykład:

Zaczerpnięte z dokumentacji sprężyny

 3
Author: flybywire,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-02-11 09:08:57

Chociaż nie jest to możliwe (jak inni ilustrowali), możesz użyć adnotacji, aby przenieść nazwę parametru i uzyskać ją mimo odbicia.

Nie jest to najczystsze rozwiązanie, ale robi swoje. Niektóre serwisy internetowe robią to, aby zachować nazwy parametrów (np.: wdrażanie WSs za pomocą glassfish).

 3
Author: WhyNotHugo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-04-16 15:13:44

Zobacz java.fasola.ConstructorProperties , jest to adnotacja przeznaczona do tego celu.

 3
Author: Markus Jevring,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-01-15 15:15:51

Więc powinieneś być w stanie zrobić:

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

Ale pewnie dostaniesz taką listę:

["int : arg0"]

Wierzę, że to zostanie naprawione w Groovy 2.5+

Więc obecnie odpowiedź brzmi:

  • Jeśli to klasa Groovy, to nie, nie możesz zdobyć nazwy, ale powinieneś być w stanie w przyszłości.
  • jeśli jest to klasa Java skompilowana Pod Java 8, powinieneś być w stanie.

Zobacz także:


Dla każdej metody, wtedy coś w stylu:

Whatever.declaredMethods
        .findAll { !it.synthetic }
        .collect { method -> 
            println method
            method.name + " -> " + method.parameters.collect { "[$it.type : $it.name]" }.join(';')
        }
        .each {
            println it
        }
 2
Author: tim_yates,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-03-03 11:39:57

Jeśli używasz eclipse, zobacz poniższy obrazek, aby umożliwić kompilatorowi przechowywanie informacji o parametrach metody

Tutaj wpisz opis obrazka

 1
Author: Hazim,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-05-23 12:34:14

Nazwy parametrów są użyteczne tylko dla kompilatora. Gdy kompilator generuje plik klasy, nazwy parametrów nie są uwzględniane - lista argumentów metody składa się tylko z Liczby i typów jej argumentów. Tak więc niemożliwe byłoby odzyskanie nazwy parametru za pomocą reflection ( jak zaznaczono w twoim pytaniu) - nigdzie nie istnieje.

Jeśli jednak użycie reflection nie jest trudnym wymogiem, można pobrać tę informację bezpośrednio z kodu źródłowego (zakładając masz).

 0
Author: danben,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-02-10 15:14:29

Aby dodać moje 2 centy; informacja o parametrze jest dostępna w pliku klasy "do debugowania", gdy używasz javac-g do kompilacji źródła. I jest dostępny dla APT, ale będziesz potrzebował adnotacji, więc nie ma dla Ciebie zastosowania. (Ktoś mówił o czymś podobnym 4-5 lat temu tutaj: http://forums.java.net/jive/thread.jspa?messageID=13467&tstart=0 )

Ogólnie rzecz biorąc, nie można go uzyskać, jeśli nie pracujesz bezpośrednio na plikach źródłowych(podobnie jak APT robi w czasie kompilacji).

 0
Author: Elister,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-02-10 16:00:33

Jak stwierdził @Bozho, można to zrobić, jeśli podczas kompilacji zawarte są informacje o debugowaniu. Tu jest dobra odpowiedź...

Jak uzyskać nazwy parametrów konstruktorów obiektu (odbicie)? by @ AdamPaynter

...korzystanie z biblioteki ASM. Zestawiłem przykład pokazujący, jak można osiągnąć swój cel.

Po pierwsze, zacznij od pom.xml z tymi zależnościami.

<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>
Więc ta klasa powinna robić, co chcesz. Wystarczy powołać się na metoda statyczna 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;
    }
}
Oto przykład z testem jednostkowym.
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) {
        }

    }
}

Pełny Przykład znajdziesz na GitHub

Caveats

  • zmieniłem nieco oryginalne rozwiązanie z @AdamPaynter, aby działało na metody. Jeśli dobrze zrozumiałem, jego rozwiązanie działa tylko z konstruktorami.
  • To rozwiązanie nie działa z metodami static. Jest to spowodowane tym, że w tym przypadku liczba argumentów zwracanych przez ASM jest różna, ale coś, co można łatwo naprawić.
 0
Author: danidemi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:17:54