Могу ли я получить имя параметра метода, используя отражение Java?

Если у меня есть такой класс:

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

есть ли способ узнать, что aMethod использует параметр с именем aParamэто типа int?

15 ответов

Решение

Подвести итоги:

  • Получение имен параметров возможно, если во время компиляции включена отладочная информация. Смотрите этот ответ для более подробной информации
  • в противном случае получение имен параметров невозможно
  • получить тип параметра возможно, используя method.getParameterTypes()

Для написания функций автозаполнения для редактора (как вы указали в одном из комментариев) есть несколько вариантов:

  • использование arg0, arg1, arg2 и т.п.
  • использование intParam, stringParam, objectTypeParam, так далее.
  • используйте комбинацию вышеупомянутого - первый для непримитивных типов, а второй для примитивных типов.
  • не показывать имена аргументов вообще - только типы.

В Java 8 вы можете сделать следующее:

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

Так что для вашего класса Whatever мы можем сделать ручной тест:

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

который должен напечатать [aParam] если вы прошли -parameters аргумент вашего компилятора Java 8.

Для пользователей 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>

Для получения дополнительной информации см. Следующие ссылки:

  1. Официальное руководство по Java: получение имен параметров метода
  2. JEP 118: доступ к именам параметров во время выполнения
  3. Javadoc для класса параметров

Библиотека Paranamer была создана для решения этой же проблемы.

Он пытается определить имена методов несколькими различными способами. Если класс был скомпилирован с отладкой, он может извлечь информацию, прочитав байт-код класса.

Другой способ - внедрить закрытый статический член в байт-код класса после его компиляции, но до его помещения в jar-файл. Затем он использует отражение, чтобы извлечь эту информацию из класса во время выполнения.

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

У меня были проблемы с использованием этой библиотеки, но в итоге я все же заработал. Я надеюсь сообщить о проблемах сопровождающему.

См. org.springframework.core.DefaultParameterNameDiscoverer класс

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

Да.
Код должен быть скомпилирован с Java 8-совместимым компилятором с включенной опцией для хранения формальных имен параметров (опция-parameters).
Тогда этот фрагмент кода должен работать:

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

Вы можете получить метод с отражением и определить его типы аргументов. Проверьте http://java.sun.com/j2se/1.4.2/docs/api/java/lang/reflect/Method.html

Однако вы не можете сказать имя используемого аргумента.

Смотрите java.beans.ConstructorProperties, это аннотация, предназначенная именно для этого.

Хотя это невозможно (как проиллюстрировали другие), вы можете использовать аннотацию для переноса имени параметра и получить его через отражение.

Не самое чистое решение, но оно выполняет свою работу. Некоторые веб-сервисы фактически делают это для сохранения имен параметров (например, развертывание WS с помощью Glassfish).

Так что вы должны быть в состоянии сделать:

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

Но вы, вероятно, получите список примерно так:

["int : arg0"]

Я верю, что это будет исправлено в Groovy 2.5+

В настоящее время ответ таков:

  • Если это класс Groovy, то нет, вы не можете получить имя, но вы должны это сделать в будущем.
  • Если это класс Java, скомпилированный в Java 8, вы должны это сделать.

Смотрите также:


Для каждого метода, то что-то вроде:

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

Если вы используете eclipse, см. изображение ниже, чтобы компилятор мог хранить информацию о параметрах метода

Это возможно, и Spring MVC 3 делает это, но я не торопился, чтобы понять, как именно.

Сопоставление имен параметров метода с именами переменных шаблона URI можно выполнить только в том случае, если ваш код скомпилирован с включенной отладкой. Если у вас не включена отладка, вы должны указать имя переменной шаблона URI в аннотации @PathVariable, чтобы связать разрешенное значение имени переменной с параметром метода. Например:

Взято из весенней документации

Как сказал @Bozho, это можно сделать, если во время компиляции будет включена отладочная информация. Здесь хороший ответ...

Как получить имена параметров конструкторов объекта (отражение)? @AdamPaynter

... используя библиотеку ASM. Я собрал пример, показывающий, как вы можете достичь своей цели.

Прежде всего, начните с pom.xml с этими зависимостями.

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

Затем этот класс должен делать то, что вы хотите. Просто вызовите статический метод 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;
    }
}

Вот пример с модульным тестом.

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

    }
}

Вы можете найти полный пример на GitHub

Предостережения

  • Я немного изменил исходное решение от @AdamPaynter, чтобы оно работало для методов. Если я правильно понял, его решение работает только с конструкторами.
  • Это решение не работает с static методы. Это связано с тем, что в этом случае количество возвращаемых ASM аргументов отличается, но это легко исправить.

Один простой метод чтения дополнительной символьной информации из байт-кода Java:

      Reflector reflector = new Reflector();
JavaMethod method = reflector.reflect(Whatever.class)
    .getMethods()
    .stream()
    .filter(m -> "aMethod".equals(m.getName()))
    .findFirst()
    .get();
String paramName = method.getParameters().getVariables().get(0).getName();
System.out.println(paramName);

Из артефакта Maven Central:

      <dependency>
    <groupId>com.intersult</groupId>
    <artifactId>coder</artifactId>
    <version>1.5</version>
</dependency>

Имена параметров полезны только для компилятора. Когда компилятор генерирует файл класса, имена параметров не включаются - список аргументов метода состоит только из числа и типов его аргументов. Поэтому было бы невозможно получить имя параметра, используя отражение (как отмечено в вашем вопросе) - оно нигде не существует.

Однако, если использование отражения не является жестким требованием, вы можете получить эту информацию непосредственно из исходного кода (при условии, что она у вас есть).

Чтобы добавить мои 2 цента; Информация о параметрах доступна в файле класса "для отладки", когда вы используете javac -g для компиляции исходного кода. И это доступно для APT, но вам понадобятся аннотации, так что бесполезно для вас. (Кто-то обсуждал нечто подобное 4-5 лет назад здесь: http://forums.java.net/jive/thread.jspa?messageID=13467&tstart=0)

Короче говоря, вы не сможете получить его, если не будете работать с исходными файлами напрямую (аналогично тому, что делает APT во время компиляции).

Другие вопросы по тегам