Se eu tiver uma classe como esta:
public class Whatever
{
public void aMethod(int aParam);
}
existe alguma maneira de saber que aMethod
usa um parâmetro chamado aParam
, que é do tipo int
?
Se eu tiver uma classe como esta:
public class Whatever
{
public void aMethod(int aParam);
}
existe alguma maneira de saber que aMethod
usa um parâmetro chamado aParam
, que é do tipo int
?
Respostas:
Para resumir:
method.getParameterTypes()
Para escrever a funcionalidade de preenchimento automático para um editor (como você afirmou em um dos comentários), existem algumas opções:
arg0
, arg1
, arg2
etc.intParam
, stringParam
, objectTypeParam
, etc.No Java 8, você pode fazer o seguinte:
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(){}
}
Assim, para a sua turma Whatever
, podemos fazer um teste manual:
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;
}
}
}
}
que deve ser impresso [aParam]
se você passou o -parameters
argumento para o seu compilador Java 8.
Para usuários do 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>
Para mais informações, consulte os seguintes links:
A biblioteca Paranamer foi criada para resolver esse mesmo problema.
Ele tenta determinar os nomes dos métodos de algumas maneiras diferentes. Se a classe foi compilada com a depuração, ela pode extrair as informações lendo o bytecode da classe.
Outra maneira é injetar um membro estático privado no bytecode da classe após a compilação, mas antes de ser colocado em um jar. Em seguida, ele usa a reflexão para extrair essas informações da classe em tempo de execução.
https://github.com/paul-hammant/paranamer
Eu tive problemas ao usar esta biblioteca, mas consegui funcioná-la no final. Espero relatar os problemas ao mantenedor.
ParameterNAmesNotFoundException
consulte a classe org.springframework.core.DefaultParameterNameDiscoverer
DefaultParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer();
String[] params = discoverer.getParameterNames(MathUtils.class.getMethod("isPrime", Integer.class));
Sim.
O código deve ser compilado com o compilador compatível com Java 8, com a opção de armazenar nomes de parâmetros formais ativados ( opção -parameters ).
Então esse trecho de código deve funcionar:
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());
}
}
Você pode recuperar o método com reflexão e detectar seus tipos de argumento. Verifique http://java.sun.com/j2se/1.4.2/docs/api/java/lang/reflect/Method.html#getParameterTypes%28%29
No entanto, você não pode dizer o nome do argumento usado.
É possível e o Spring MVC 3 faz isso, mas não demorei muito para ver exatamente como.
A correspondência de nomes de parâmetros de métodos com nomes de variáveis de modelo de URI só pode ser feita se seu código for compilado com a depuração ativada. Se você não tiver a depuração ativada, deverá especificar o nome do nome da variável do Modelo de URI na anotação @PathVariable para vincular o valor resolvido do nome da variável a um parâmetro de método. Por exemplo:
Retirado da documentação da primavera
Embora não seja possível (como outros ilustraram), você pode usar uma anotação para substituir o nome do parâmetro e obtê-lo através de reflexão.
Não é a solução mais limpa, mas faz o trabalho. Alguns serviços da Web realmente fazem isso para manter os nomes dos parâmetros (por exemplo: implantar WSs com glassfish).
Consulte java.beans.ConstructorProperties , é uma anotação projetada para fazer exatamente isso.
Então você deve ser capaz de:
Whatever.declaredMethods
.find { it.name == 'aMethod' }
.parameters
.collect { "$it.type : $it.name" }
Mas você provavelmente obterá uma lista como esta:
["int : arg0"]
Eu acredito que isso será corrigido no Groovy 2.5+
Então, atualmente, a resposta é:
Veja também:
Para cada método, algo como:
Whatever.declaredMethods
.findAll { !it.synthetic }
.collect { method ->
println method
method.name + " -> " + method.parameters.collect { "[$it.type : $it.name]" }.join(';')
}
.each {
println it
}
aMethod
. Eu quero obtê-lo para todos os métodos em uma classe.
antlr
para obter nomes de parâmetros para isso?
Como o @Bozho afirmou, é possível fazê-lo se as informações de depuração forem incluídas durante a compilação. Há uma boa resposta aqui ...
Como obter os nomes dos parâmetros dos construtores de um objeto (reflexão)? por @AdamPaynter
... usando a biblioteca ASM. Eu montei um exemplo mostrando como você pode alcançar seu objetivo.
Primeiro de tudo, comece com um pom.xml com essas dependências.
<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>
Então, essa classe deve fazer o que você deseja. Apenas invoque o método estático 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;
}
}
Aqui está um exemplo com um teste de unidade.
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) {
}
}
}
Você pode encontrar o exemplo completo no GitHub
static
métodos. Nesse caso, o número de argumentos retornados pelo ASM é diferente, mas é algo que pode ser facilmente corrigido.Os nomes de parâmetros são úteis apenas para o compilador. Quando o compilador gera um arquivo de classe, os nomes dos parâmetros não são incluídos - a lista de argumentos de um método consiste apenas no número e nos tipos de argumentos. Portanto, seria impossível recuperar o nome do parâmetro usando reflexão (conforme marcado na sua pergunta) - ele não existe em nenhum lugar.
No entanto, se o uso da reflexão não for um requisito difícil, você poderá recuperar essas informações diretamente do código-fonte (supondo que você as tenha).
Parameter names are only useful to the compiler.
Errado. Veja a biblioteca Retrofit. Ele usa interfaces dinâmicas para criar solicitações de API REST. Um de seus recursos é a capacidade de definir nomes de espaços reservados nos caminhos da URL e substituí-los pelos nomes de parâmetros correspondentes.
Para adicionar meus 2 centavos; as informações do parâmetro estão disponíveis em um arquivo de classe "para depuração" quando você usa o javac -g para compilar a fonte. E está disponível para o APT, mas você precisará de uma anotação para que não seja útil. (Alguém discutiu algo semelhante há 4-5 anos aqui: http://forums.java.net/jive/thread.jspa?messageID=13467&tstart=0 )
Em resumo, você não pode obtê-lo, a menos que trabalhe diretamente nos arquivos de origem (semelhante ao que o APT faz no momento da compilação).