Si tengo una clase como esta:
public class Whatever
{
public void aMethod(int aParam);
}
¿Hay alguna manera de saber que aMethod
usa un parámetro llamado aParam
, que es de tipo int
?
Si tengo una clase como esta:
public class Whatever
{
public void aMethod(int aParam);
}
¿Hay alguna manera de saber que aMethod
usa un parámetro llamado aParam
, que es de tipo int
?
Respuestas:
Para resumir:
method.getParameterTypes()
En aras de escribir la funcionalidad de autocompletar para un editor (como mencionó en uno de los comentarios), hay algunas opciones:
arg0
, arg1
, arg2
etc.intParam
, stringParam
, objectTypeParam
, etc.En Java 8 puede hacer lo siguiente:
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(){}
}
Entonces, para su clase Whatever
, podemos hacer una prueba 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 debería imprimirse [aParam]
si ha pasado el -parameters
argumento a su compilador Java 8.
Para usuarios de 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 obtener más información, consulte los siguientes enlaces:
La biblioteca Paranamer fue creada para resolver este mismo problema.
Intenta determinar los nombres de los métodos de diferentes maneras. Si la clase se compiló con depuración, puede extraer la información leyendo el código de bytes de la clase.
Otra forma es inyectar un miembro estático privado en el código de bytes de la clase después de compilarlo, pero antes de colocarlo en un jar. Luego usa la reflexión para extraer esta información de la clase en tiempo de ejecución.
https://github.com/paul-hammant/paranamer
Tuve problemas al usar esta biblioteca, pero al final lo hice funcionar. Espero informar los problemas al mantenedor.
ParameterNAmesNotFoundException
vea la clase org.springframework.core.DefaultParameterNameDiscoverer
DefaultParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer();
String[] params = discoverer.getParameterNames(MathUtils.class.getMethod("isPrime", Integer.class));
Si.
Código debe ser compilado con Java 8 compilador compatible con la opción de almacenar nombres de parámetros formales activada ( -parámetros opción).
Entonces este fragmento de código debería 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());
}
}
Puede recuperar el método con reflexión y detectar sus tipos de argumento. Verifique http://java.sun.com/j2se/1.4.2/docs/api/java/lang/reflect/Method.html#getParameterTypes%28%29
Sin embargo, no puede decir el nombre del argumento utilizado.
Es posible y Spring MVC 3 lo hace, pero no me tomé el tiempo para ver exactamente cómo.
La coincidencia de los nombres de los parámetros del método con los nombres de las variables de plantilla URI solo se puede hacer si su código se compila con la depuración habilitada. Si no tiene habilitada la depuración, debe especificar el nombre del nombre de la variable de plantilla URI en la anotación @PathVariable para vincular el valor resuelto del nombre de la variable a un parámetro de método. Por ejemplo:
Tomado de la documentación de primavera
Si bien no es posible (como lo han ilustrado otros), puede usar una anotación para transferir el nombre del parámetro y obtener eso a través de la reflexión.
No es la solución más limpia, pero hace el trabajo. Algunos servicios web realmente hacen esto para mantener los nombres de los parámetros (es decir, implementar WS con glassfish).
Ver java.beans.ConstructorProperties , es una anotación diseñada para hacer exactamente esto.
Entonces deberías poder hacer:
Whatever.declaredMethods
.find { it.name == 'aMethod' }
.parameters
.collect { "$it.type : $it.name" }
Pero probablemente obtendrá una lista como esta:
["int : arg0"]
Creo que esto se solucionará en Groovy 2.5+
Entonces, actualmente, la respuesta es:
Ver también:
Para cada método, entonces 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
. Quiero obtenerlo para todos los métodos en una clase.
antlr
para obtener nombres de parámetros para esto?
Como dijo @Bozho, es posible hacerlo si se incluye información de depuración durante la compilación. Hay una buena respuesta aquí ...
¿Cómo obtener los nombres de los parámetros de los constructores de un objeto (reflexión)? por @AdamPaynter
... usando la biblioteca ASM. He preparado un ejemplo que muestra cómo puedes lograr tu objetivo.
En primer lugar, comience con un pom.xml con estas dependencias.
<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>
Entonces, esta clase debe hacer lo que quieras. Solo invoca el 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;
}
}
Aquí hay un ejemplo con una prueba unitaria.
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) {
}
}
}
Puedes encontrar el ejemplo completo en GitHub
static
métodos. Esto se debe a que en este caso el número de argumentos devueltos por ASM es diferente, pero es algo que se puede solucionar fácilmente.Los nombres de los parámetros solo son útiles para el compilador. Cuando el compilador genera un archivo de clase, los nombres de los parámetros no se incluyen: la lista de argumentos de un método solo consta del número y los tipos de sus argumentos. Por lo tanto, sería imposible recuperar el nombre del parámetro utilizando la reflexión (como está etiquetado en su pregunta); no existe en ningún lado.
Sin embargo, si el uso de la reflexión no es un requisito difícil, puede recuperar esta información directamente del código fuente (suponiendo que la tenga).
Parameter names are only useful to the compiler.
Incorrecto. Mire la biblioteca Retrofit. Utiliza interfaces dinámicas para crear solicitudes de API REST. Una de sus características es la capacidad de definir nombres de marcadores de posición en las rutas URL y reemplazar esos marcadores de posición con sus correspondientes nombres de parámetros.
Para agregar mis 2 centavos; la información de parámetros está disponible en un archivo de clase "para depuración" cuando usa javac -g para compilar la fuente. Y está disponible para APT, pero necesitarás una anotación, así que no te servirá. (Alguien discutió algo similar hace 4-5 años aquí: http://forums.java.net/jive/thread.jspa?messageID=13467&tstart=0 )
En resumen, no puede obtenerlo a menos que trabaje directamente en los archivos de origen (similar a lo que hace APT en tiempo de compilación).