Como obter nomes de parâmetro de método no Java 8 usando reflection?

O Java 8 tem a capacidade de adquirir nomes de parâmetros de método usando a API de Reflexão.

  1. Como posso obter esses nomes de parâmetro de método?

  2. Conforme meu conhecimento, os arquivos de class não armazenam nomes de parâmetros formais. Como posso obtê-los usando a reflection?

Como posso obter esses nomes de parâmetros de método (algum código de exemplo)?

Basicamente, você precisa:

  • obter uma referência a uma Class
  • Da Class , obter uma referência a um Method chamando getDeclaredMethod() ou getDeclaredMethods() que retorna referências a objects de Method
  • A partir do object Method , chame (new a partir do Java 8) getParameters() que retorna uma matriz de objects Parameter
  • No object Parameter , chame getName()
 Class clz = String.class; for (Method m : clz.getDeclaredMethods()) { System.err.println(m.getName()); for (Parameter p : m.getParameters()) { System.err.println(" " + p.getName()); } } 

Saída:

 ... indexOf arg0 indexOf arg0 arg1 ... 

Além disso, conforme meu conhecimento, os arquivos .class não armazenam parâmetros formais. Então, como posso obtê-los usando a reflection?

Veja o javadoc para Parameter.getName() :

Se o nome do parâmetro estiver presente , esse método retornará o nome fornecido pelo arquivo de class . Caso contrário , este método sintetiza um nome da forma argN , onde N é o índice do parâmetro no descritor do método que declara o parâmetro.

Se um JDK suporta isso, é uma implementação específica (como você pode ver na saída acima, o build 125 do JDK 8 não suporta isso). O formato de arquivo de class suporta atributos opcionais que podem ser usados ​​por uma implementação específica da JVM / javac e que são ignorados por outras implementações que não suportam isso.

Note que você poderia até gerar a saída acima com arg0 , arg1 , … com pré Java 8 JVMs – tudo que você precisa saber é a contagem de parâmetros que é acessível através de Method.getParameterTypes() :

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

O que há de novo no JDK 8 é que existe uma API estendida e a possibilidade de JVMs fornecerem os nomes de parâmetros reais em vez de arg0 , arg1 , ...

O suporte a esses resources opcionais é possível através de atributos opcionais que podem ser anexados às várias estruturas de arquivos de classs. Veja 4.6. Métodos para a estrutura method_info dentro de um arquivo de class. Veja também 4.7.1. Definindo e Nomeando Novos Atributos na Especificação da JVM.

Já que com o JDK 8, a versão do arquivo de class será incrementada para 52, também seria possível alterar o formato do arquivo para suportar esse recurso.

Veja também JEP 118: Acesso aos Nomes de parameters em Tempo de Execução para mais informações e alternativas de implementação. O modelo de implementação proposto é adicionar um atributo opcional que armazena os nomes dos parâmetros. Como o formato de arquivo de class já suporta esses atributos opcionais, isso seria possível de forma que os arquivos de class ainda possam ser usados ​​por JVMs mais antigas, onde eles são simplesmente ignorados conforme exigido pela especificação:

As implementações da Java Virtual Machine são necessárias para ignorar silenciosamente atributos que eles não reconhecem.

Atualizar

Como sugerido por @assylias, a fonte precisa ser compilada com os parâmetros de opção de linha de comando do javac para adicionar os metadados da reflection do nome do parâmetro ao arquivo de class. No entanto, isso obviamente afetará apenas o código compilado com essa opção - o código acima ainda imprimirá arg0 , arg1 etc., já que as bibliotecas de tempo de execução não serão compiladas com esse sinalizador e, portanto, não conterão as inputs necessárias nos arquivos de class.

Obrigado Andreas, mas finalmente eu tenho a solução completa do oracle Tutorials em Method Parameters

Diz,

Você pode obter os nomes dos parâmetros formais de qualquer método ou construtor com o método java.lang.reflect.Executable.getParameters. (As classs Method e Constructor estendem a class Executable e, portanto, herdam o método Executable.getParameters.) No entanto, os arquivos .class não armazenam nomes de parâmetros formais por padrão. Isso ocorre porque muitas ferramentas que produzem e consomem arquivos de class podem não esperar o espaço físico e estático maior dos arquivos .class que contêm nomes de parâmetros. Em particular, essas ferramentas teriam que manipular arquivos .class maiores, e a Java Virtual Machine (JVM) usaria mais memory. Além disso, alguns nomes de parâmetros, como segredo ou senha, podem expor informações sobre methods sensíveis à segurança.

Para armazenar nomes de parâmetros formais em um arquivo .class específico, e assim permitir que a API de Reflexão recupere nomes de parâmetros formais, compile o arquivo de origem com a opção -parameters para o compilador javac .

Como compilar

Remember to compile the with the -parameters compiler option

Saída esperada (para o exemplo completo, visite o link mencionado acima)

java MethodParameterSpy ExampleMethods

Este comando imprime o seguinte:

 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) 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  void ExampleMethods.genericMethod(T[],java.util.Collection) 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 

de acordo com as informações da Store sobre parâmetros de método (utilizáveis ​​através de reflection) em intellij 13 , o equivalente a “parâmetros-javac” dentro do Eclipse IDE é ‘Armazenar informações sobre parâmetros de método (utilizáveis ​​através de reflection)’ em Window -> Preferences -> Java – > Compilador.

Você pode usar o lib Paranamer ( https://github.com/paul-hammant/paranamer )

Exemplo de código que funciona para mim:

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

Se você usar o Maven, coloque essa dependência em seu pom.xml:

  com.thoughtworks.paranamer paranamer 2.8