Jeśli mam taką klasę:
public class Whatever
{
public void aMethod(int aParam);
}
czy istnieje sposób, aby dowiedzieć się, że aMethod
używa parametru o nazwie aParam
, czyli typu int
?
Jeśli mam taką klasę:
public class Whatever
{
public void aMethod(int aParam);
}
czy istnieje sposób, aby dowiedzieć się, że aMethod
używa parametru o nazwie aParam
, czyli typu int
?
Odpowiedzi:
Podsumowując:
method.getParameterTypes()
Ze względu na pisanie funkcji autouzupełniania dla edytora (jak wspomniałeś w jednym z komentarzy) jest kilka opcji:
arg0
, arg1
, arg2
itd.intParam
, stringParam
, objectTypeParam
, itd.W Javie 8 możesz wykonać następujące czynności:
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(){}
}
Więc dla Twojej klasy Whatever
możemy wykonać test manualny:
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;
}
}
}
}
który powinien zostać wydrukowany, [aParam]
jeśli przekazałeś -parameters
argument do kompilatora Java 8.
Dla użytkowników 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>
Aby uzyskać więcej informacji, zobacz następujące łącza:
Biblioteka Paranamer została stworzona, aby rozwiązać ten sam problem.
Próbuje określić nazwy metod na kilka różnych sposobów. Jeśli klasa została skompilowana z debugowaniem, może wyodrębnić informacje, odczytując kod bajtowy klasy.
Innym sposobem jest wstrzyknięcie prywatnego statycznego elementu członkowskiego do kodu bajtowego klasy po jej skompilowaniu, ale przed umieszczeniem go w słoiku. Następnie używa odbicia, aby wyodrębnić te informacje z klasy w czasie wykonywania.
https://github.com/paul-hammant/paranamer
Miałem problemy z używaniem tej biblioteki, ale w końcu udało mi się ją uruchomić. Mam nadzieję zgłosić ten problem opiekunowi.
ParameterNAmesNotFoundException
zobacz klasę org.springframework.core.DefaultParameterNameDiscoverer
DefaultParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer();
String[] params = discoverer.getParameterNames(MathUtils.class.getMethod("isPrime", Integer.class));
Tak.
Kod musi być skompilowany za pomocą kompilatora zgodnego z Javą 8 z włączoną opcją przechowywania formalnych nazw parametrów ( opcja -parameters ).
Wtedy ten fragment kodu powinien działać:
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());
}
}
Możesz pobrać metodę z odbiciem i wykryć jej typy argumentów. Sprawdź http://java.sun.com/j2se/1.4.2/docs/api/java/lang/reflect/Method.html#getParameterTypes%28%29
Jednak nie możesz podać nazwy użytego argumentu.
Jest to możliwe i Spring MVC 3 to robi, ale nie poświęciłem czasu, aby zobaczyć, jak to zrobić.
Dopasowanie nazw parametrów metod do nazw zmiennych szablonu URI można wykonać tylko wtedy, gdy kod jest kompilowany z włączonym debugowaniem. Jeśli debugowanie nie jest włączone, należy określić nazwę zmiennej szablonu URI w adnotacji @PathVariable, aby powiązać rozstrzygniętą wartość nazwy zmiennej z parametrem metody. Na przykład:
Zaczerpnięte z dokumentacji wiosennej
Chociaż nie jest to możliwe (jak inni rysunku), to mógłby użyć adnotacji o przeniesienie nazwę parametru, a uzyskanie że chociaż refleksji.
Nie jest to najczystsze rozwiązanie, ale spełnia swoje zadanie. Niektóre usługi sieciowe faktycznie robią to, aby zachować nazwy parametrów (np. Wdrażanie WS z Glassfish).
Zobacz java.beans.ConstructorProperties , jest to adnotacja zaprojektowana właśnie do tego.
Więc powinieneś być w stanie zrobić:
Whatever.declaredMethods
.find { it.name == 'aMethod' }
.parameters
.collect { "$it.type : $it.name" }
Ale prawdopodobnie otrzymasz taką listę:
["int : arg0"]
Wierzę, że zostanie to naprawione w Groovy 2.5+
Tak więc obecnie odpowiedź brzmi:
Zobacz też:
Dla każdej metody wtedy coś takiego:
Whatever.declaredMethods
.findAll { !it.synthetic }
.collect { method ->
println method
method.name + " -> " + method.parameters.collect { "[$it.type : $it.name]" }.join(';')
}
.each {
println it
}
aMethod
. Chcę to uzyskać dla wszystkich metod w klasie.
antlr
do uzyskania nazw parametrów dla tego?
Jak stwierdził @Bozho, można to zrobić, jeśli podczas kompilacji zostaną uwzględnione informacje o debugowaniu. Tutaj jest dobra odpowiedź ...
Jak uzyskać nazwy parametrów konstruktorów obiektu (odbicie)? przez @AdamPaynter
... używając biblioteki ASM. Przygotowałem przykład pokazujący, jak możesz osiągnąć swój cel.
Przede wszystkim zacznij od pom.xml z tymi zależnościami.
<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>
Następnie ta klasa powinna robić, co chcesz. Po prostu wywołaj metodę statyczną 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;
}
}
Oto przykład z testem jednostkowym.
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) {
}
}
}
Kompletny przykład można znaleźć na GitHub
static
metodami. Dzieje się tak dlatego, że w tym przypadku liczba argumentów zwracanych przez ASM jest różna, ale jest to coś, co można łatwo naprawić.Nazwy parametrów są przydatne tylko dla kompilatora. Gdy kompilator generuje plik klasy, nazwy parametrów nie są uwzględniane - lista argumentów metody składa się tylko z liczby i typów jej argumentów. Dlatego nie byłoby możliwe pobranie nazwy parametru za pomocą refleksji (tak jak zostało to zaznaczone w Twoim pytaniu) - nigdzie nie istnieje.
Jeśli jednak użycie odbicia nie jest trudnym wymaganiem, możesz pobrać te informacje bezpośrednio z kodu źródłowego (zakładając, że je masz).
Parameter names are only useful to the compiler.
Źle. Spójrz na bibliotekę Retrofit. Wykorzystuje dynamiczne interfejsy do tworzenia żądań REST API. Jedną z jego funkcji jest możliwość definiowania nazw symboli zastępczych w ścieżkach adresów URL i zastępowania tych symboli zastępczych odpowiadającymi im nazwami parametrów.
Aby dodać moje 2 centy; Informacje o parametrach są dostępne w pliku klasy „do debugowania”, gdy używasz javac -g do kompilowania źródła. Jest dostępny dla APT, ale będziesz potrzebować adnotacji, więc nie będzie dla ciebie pożytku. (Ktoś omawiał coś podobnego 4-5 lat temu tutaj: http://forums.java.net/jive/thread.jspa?messageID=13467&tstart=0 )
Krótko mówiąc, nie możesz tego dostać, chyba że pracujesz bezpośrednio na plikach źródłowych (podobnie do tego, co robi APT w czasie kompilacji).