Как получить имена параметров метода в Java 8 с использованием отражения?

Java 8 имеет возможность приобретать имена параметров метода с использованием Reflection API.

  • Как я могу получить эти имена параметров метода?

  • Как известно, файлы классов не хранят формальные имена параметров. Как я могу получить их с помощью отражения?

Ответ 1

Как я могу получить эти имена параметров метода (некоторый пример кода)?

В принципе, вам необходимо:

  • получить ссылку на Class
  • Из Class получить ссылку на Method, вызвав getDeclaredMethod() или getDeclaredMethods(), который возвращает ссылки на объекты Method
  • Из объекта Method вызов (новый как Java 8) getParameters(), который возвращает массив объектов Parameter
  • На объекте Parameter вызовите getName()
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());
   }
}

Вывод:

...
indexOf
  arg0
indexOf
  arg0
  arg1
...

Также в соответствии с моими знаниями .class файлы не хранят формальный параметр. Тогда как я могу получить их с помощью отражения?

См. javadoc для Parameter.getName():

... Если имя параметра присутствует, то этот метод возвращает имя, предоставленное файлом класса. В противном случае этот метод синтезирует имя формы argN, где N - это индекс параметра в дескрипторе метода, объявляющего параметр.

Независимо от того, поддерживает ли JDK это, специфична для реализации (как вы видите, вид выше, сборка 125 JDK 8 не поддерживает его). Формат файла класса поддерживает необязательные атрибуты, которые могут использоваться конкретной реализацией JVM/javac и которые игнорируются другими реализациями, которые его не поддерживают.

Обратите внимание, что вы даже можете генерировать вышеуказанный вывод с помощью arg0, arg1,... с JVM до Java 8 - все, что вам нужно знать, - это счетчик параметров, доступный через Method.getParameterTypes():

Class<String> clz = String.class;
for (Method m : clz.getDeclaredMethods()) {
  System.err.println(m.getName());
  int paramCount = m.getParameterTypes().length;
  for (int i = 0;  i < paramCount;  i++) {
    System.err.println("  arg" + i);
  }
}

Что нового в JDK 8 заключается в том, что для JVM для расширенного API и возможности для предоставления имен реальных параметров вместо arg0, arg1,...

Поддержка таких дополнительных функций возможна с помощью дополнительных атрибутов, которые могут быть прикреплены к различным структурам файлов классов. См. 4.6. Методы для структуры method_info в файле класса. См. Также 4.7.1. Определение и назначение новых атрибутов в спецификации JVM.

Так как с JDK 8 версия файла класса будет увеличена до 52, было бы также возможно изменить формат файла для поддержки этой функции.

См. также JEP 118: Доступ к именам параметров во время выполнения для получения дополнительной информации и альтернатив реализации. Предложенная модель реализации заключается в добавлении необязательного атрибута, в котором хранятся имена параметров. Поскольку формат файла классов уже поддерживает эти необязательные атрибуты, это будет даже возможно, чтобы файлы классов все еще могли использоваться более старыми JVM, где они просто игнорируются по требованию спецификации:

Реализации Java Virtual Machine необходимы для молча игнорирования атрибутов, которые они не распознают.

Обновление

Как было предложено @assylias, источник необходимо скомпилировать с помощью опции javac командной строки -parameters, чтобы добавить метаданные для отражения имени параметра в файл класса. Однако это, конечно же, повлияет только на код, скомпилированный с этой опцией - вышеприведенный код будет печатать arg0, arg1 и т.д., Так как библиотеки времени выполнения не скомпилируются с этим флагом и, следовательно, не содержат необходимых записей в файлы классов.

Ответ 2

Спасибо Андреасу, но, наконец, я получил полное решение из уроков оракула по Параметры метода

В нем говорится:

Вы можете получить имена формальных параметров любого метода или конструктор с методом java.lang.reflect.Executable.getParameters. (Методы классов и Конструктор расширяет класс Executable и, следовательно, наследует метод Executable.getParameters.) Однако файлы .class не сохраняют формальные имена параметров по умолчанию. Это связано с тем, что многие инструменты, которые производить и потреблять файлы классов, может не ожидаться больших статических и динамический размер .class файлов, содержащих имена параметров. В В частности, эти инструменты должны обрабатывать файлы большего размера. виртуальная машина Java (JVM) будет использовать больше памяти. К тому же, некоторые имена параметров, такие как секрет или пароль, могут информация о чувствительных к безопасности методах.

Сохранение формальных имен параметров в определенном файле .class и, следовательно, включить API Reflection для получения формальных имен параметров, компиляции исходный файл с параметром -parameters компилятору javac.

Как скомпилировать

Remember to compile the with the -parameters compiler option

Ожидаемый результат (Полный пример см. в ссылке выше)

java MethodParameterSpy ExampleMethods

Эта команда печатает следующее:

Number of constructors: 1

Constructor #1
public ExampleMethods()

Number of declared constructors: 1

Declared constructor #1
public ExampleMethods()

Number of methods: 4

Method #1
public boolean ExampleMethods.simpleMethod(java.lang.String,int)
             Return type: boolean
     Generic return type: boolean
         Parameter class: class java.lang.String
          Parameter name: stringParam
               Modifiers: 0
            Is implicit?: false
        Is name present?: true
           Is synthetic?: false
         Parameter class: int
          Parameter name: intParam
               Modifiers: 0
            Is implicit?: false
        Is name present?: true
           Is synthetic?: false

Method #2
public int ExampleMethods.varArgsMethod(java.lang.String...)
             Return type: int
     Generic return type: int
         Parameter class: class [Ljava.lang.String;
          Parameter name: manyStrings
               Modifiers: 0
            Is implicit?: false
        Is name present?: true
           Is synthetic?: false

Method #3
public boolean ExampleMethods.methodWithList(java.util.List<java.lang.String>)
             Return type: boolean
     Generic return type: boolean
         Parameter class: interface java.util.List
          Parameter name: listParam
               Modifiers: 0
            Is implicit?: false
        Is name present?: true
           Is synthetic?: false

Method #4
public <T> void ExampleMethods.genericMethod(T[],java.util.Collection<T>)
             Return type: void
     Generic return type: void
         Parameter class: class [Ljava.lang.Object;
          Parameter name: a
               Modifiers: 0
            Is implicit?: false
        Is name present?: true
           Is synthetic?: false
         Parameter class: interface java.util.Collection
          Parameter name: c
               Modifiers: 0
            Is implicit?: false
        Is name present?: true
           Is synthetic?: false

Ответ 3

Вы можете использовать Paranamer lib (https://github.com/paul-hammant/paranamer)

Пример кода, который работает для меня:

import com.thoughtworks.paranamer.AnnotationParanamer;
import com.thoughtworks.paranamer.BytecodeReadingParanamer;
import com.thoughtworks.paranamer.CachingParanamer;
import com.thoughtworks.paranamer.Paranamer;

Paranamer info = new CachingParanamer(new AnnotationParanamer(new BytecodeReadingParanamer()));

Method method = Foo.class.getMethod(...);
String[] parameterNames = info.lookupParameterNames(method);

Если вы используете Maven, поместите эту зависимость в свой pom.xml:

<dependency>
    <groupId>com.thoughtworks.paranamer</groupId>
    <artifactId>paranamer</artifactId>
    <version>2.8</version>
</dependency>

Ответ 4

в соответствии с Хранить информацию о параметрах метода (можно использовать с помощью отражения) в intellij 13, эквивалент "javac -параметров" в среде Eclipse IDE" Хранить информацию о Параметры метода (можно использовать с помощью отражения) 'в окне → Настройки → Java → Компилятор.