Чи можу я отримати ім'я параметра методу за допомогою відображення Java?


124

Якщо у мене такий клас:

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

чи є спосіб дізнатися, що aMethodвикористовується параметр з назвою aParam, тобто тип int?


10
7 відповідей, і ніхто не згадував термін "метод підпису". Це в основному те, що ви можете вступити в відображення за допомогою відображення, це означає: відсутність імен параметрів.
ewernli

3
це це можливо, побачити моя відповідь нижче
едсу

4
Через 6 років це можливо через роздуми на Java 8 , дивіться цю відповідь
ТА

Відповіді:


90

Узагальнити:

  • отримання імен параметрів є можливим , якщо налагоджувальна інформація включається під час компіляції. Дивіться цю відповідь для отримання більш детальної інформації
  • в іншому випадку отримання імен параметрів є НЕ можливо
  • отримати тип параметра можливо, використовуючи method.getParameterTypes()

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

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

4
Чи можливо це за допомогою інтерфейсів? Я досі не міг знайти спосіб отримати імена параметрів інтерфейсу.
Цемо

@Cemo: чи вдалося вам знайти спосіб отримання імен параметрів інтерфейсу?
Вайбхав Гупта

Просто додам, тому для отримання неоднозначного створення об'єкта з примітивних типів весна потребує анотації @ConstructorProperties.
Бхарат

102

У 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 для класу Параметр

2
Я не знаю, чи змінили вони аргументи для плагіна компілятора, але з останньою (3.5.1 станом на даний момент) мені довелося використовувати аргументи компілятора в розділі конфігурації: <configuration> <compilerArgs> <arg> - параметри </arg> </compilerArgs> </configuration>
макс

15

Для вирішення цієї ж проблеми була створена бібліотека Паранамера.

Він намагається визначити назви методів кількома різними способами. Якщо клас був складений з налагодженням, він може витягнути інформацію, прочитавши байт-код класу.

Інший спосіб полягає в тому, щоб він вводив приватний статичний член у байт-код класу після його складання, але перед тим, як його розмістити в банку. Потім він використовує рефлексію для вилучення цієї інформації з класу під час виконання.

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

У мене були проблеми з використанням цієї бібліотеки, але я зрештою змусив її працювати. Я сподіваюся повідомити про проблеми технічному обслуговувачу.


Я намагаюся використовувати паранамер всередині Android apk. Але я отримуюParameterNAmesNotFoundException
Рільван

10

див. клас org.springframework.core.DefaultParameterNameDiscoverer

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

10

Так.
Код повинен бути зібраний з компілятором, сумісним з Java 8, з можливістю зберігати ввімкнені формальні імена параметрів ( параметр -параметри ).
Тоді цей фрагмент коду повинен працювати:

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

Спробував це, і це спрацювало! Одна порада, мені довелося відновити ваш проект, щоб ці конфігураційні компілятори набули чинності.
ishmaelMakitla

5

Ви можете отримати метод за допомогою відображення та виявити його типи аргументів. Перевірте http://java.sun.com/j2se/1.4.2/docs/api/java/lang/reflect/Method.html#getParameterTypes%28%29

Однак назвати використаний аргумент не можна.


17
Воістину, це все можливо. Однак його питання було чітко про назву параметра . Перевірте теппіцит.
BalusC

1
І я цитую: "Однак ви не можете сказати назву використовуваного аргументу". просто прочитайте мою відповідь -_-
Джонко

3
Питання не було сформульовано так, що він не знав про отримання типу.
BalusC

3

Це можливо, і Spring MVC 3 це робить, але я не знайшов часу, щоб зрозуміти, як саме.

Зіставлення імен параметрів методу до імен змінних шаблонів URI можна здійснити лише в тому випадку, якщо ваш код компільований з увімкненою налагодженням. Якщо ви не ввімкнули налагодження, потрібно вказати ім'я змінної шаблону URI в анотації @PathVariable, щоб прив’язати розв'язане значення імені змінної до параметра методу. Наприклад:

Взяте з весняної документації


org.springframework.core.Conventions.getVariableName (), але я гадаю, що у вас має бути cglib як залежність
Radu Toader

3

Хоча це неможливо (як це показали інші), ви можете використовувати анотацію для перенесення назви параметра та отримати це, хоча і відображення.

Не найчистіше рішення, але воно робить роботу. Деякі веб-сервіси насправді роблять це для збереження назв параметрів (тобто: розгортання WS із скловолокном).



3

Отже, ви повинні вміти робити:

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
        }

Я також не хочу вказувати назву методу aMethod. Я хочу отримати його для всіх методів у класі.
snoop

@snoop додав приклад отримання кожного несинтетичного методу
tim_yates

Не можемо ми використовувати antlrдля цього імена параметрів?
snoop

2

якщо ви використовуєте затемнення, див. нижче зображення, щоб дозволити компілятору зберігати інформацію про параметри методу

введіть тут опис зображення


2

Як заявив @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, різна, але це щось, що можна легко виправити.

0

Імена параметрів корисні лише компілятору. Коли компілятор генерує файл класу, імена параметрів не включаються - список аргументів методу складається лише з кількості та типів його аргументів. Тому неможливо було б отримати ім’я параметра за допомогою відображення (як позначено у вашому запитанні) - воно ніде не існує.

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


2
Назва параметра корисна не лише компілятору, вони також передають інформацію споживачеві бібліотеки, припустимо, я написав клас StrangeDate, який мав додавати метод (int day, int hour, int minute), якщо ви перейшли до цього і знайшли метод add (int arg0, int arg1, int arg2) наскільки корисним було б це?
Девід Уотерс

Вибачте - ви повністю зрозуміли мою відповідь. Будь ласка, перечитайте це в контексті запитання.
danben

2
Придбання назв параметрів є великою перевагою для рефлексивного виклику цього методу. Це не тільки корисно для укладача, навіть у контексті питання.
corsiKa

Parameter names are only useful to the compiler.Неправильно. Подивіться на бібліотеку модернізації. Він використовує динамічні інтерфейси для створення REST API-запитів. Однією з його особливостей є можливість визначати імена заповнювачів у шляхах URL та замінювати ці заповнювачі відповідними назвами параметрів.
TheRealChx101

0

Щоб додати мої 2 копійки; Інформація про параметр доступна у файлі класу "для налагодження", коли ви використовуєте javac -g для компіляції джерела. І він доступний для APT, але вам знадобиться анотація, тому вам не потрібна. (Хтось обговорював щось подібне 4-5 років тому тут: http://forums.java.net/jive/thread.jspa?messageID=13467&tstart=0 )

Загалом коротко кажучи, ви не можете його отримати, якщо безпосередньо не працюєте над вихідними файлами (подібно до того, що APT робить під час компіляції).

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.