ถ้าฉันมีชั้นเรียนเช่นนี้:
public class Whatever
{
public void aMethod(int aParam);
}
จะมีวิธีใดที่จะรู้ว่าaMethod
ใช้พารามิเตอร์การตั้งชื่อaParam
ที่เป็นประเภทint
?
ถ้าฉันมีชั้นเรียนเช่นนี้:
public class Whatever
{
public void aMethod(int aParam);
}
จะมีวิธีใดที่จะรู้ว่าaMethod
ใช้พารามิเตอร์การตั้งชื่อaParam
ที่เป็นประเภทint
?
คำตอบ:
สรุป:
method.getParameterTypes()
เพื่อประโยชน์ในการเขียนฟังก์ชันการเติมข้อความอัตโนมัติสำหรับตัวแก้ไข (ตามที่คุณระบุไว้ในความคิดเห็น) มีตัวเลือกสองสามตัวเลือก:
arg0
, arg1
, arg2
ฯลฯintParam
, stringParam
, objectTypeParam
ฯลฯใน Java 8 คุณสามารถทำสิ่งต่อไปนี้:
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(){}
}
ดังนั้นสำหรับชั้นเรียนของคุณWhatever
เราสามารถทำการทดสอบด้วยตนเองได้:
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;
}
}
}
}
ซึ่งควรพิมพ์[aParam]
หากคุณส่ง-parameters
อาร์กิวเมนต์ไปยังคอมไพเลอร์ Java 8 ของคุณ
สำหรับผู้ใช้ 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>
สำหรับข้อมูลเพิ่มเติมโปรดดูลิงค์ต่อไปนี้:
ไลบรารี Paranamer ถูกสร้างขึ้นเพื่อแก้ปัญหาเดียวกันนี้
จะพยายามกำหนดชื่อวิธีการด้วยวิธีต่างๆ หากคลาสถูกคอมไพล์ด้วยการดีบักก็สามารถดึงข้อมูลโดยการอ่าน bytecode ของคลาส
อีกวิธีหนึ่งคือฉีดสมาชิกแบบคงที่ส่วนตัวลงใน bytecode ของคลาสหลังจากที่คอมไพล์แล้ว แต่ก่อนที่มันจะถูกใส่ลงในโถ จากนั้นใช้การสะท้อนเพื่อดึงข้อมูลนี้จากคลาสที่รันไทม์
https://github.com/paul-hammant/paranamer
ฉันมีปัญหาในการใช้ไลบรารีนี้ แต่สุดท้ายก็ใช้งานได้ ฉันหวังว่าจะรายงานปัญหาไปยังผู้ดูแล
ParameterNAmesNotFoundException
ดูคลาส org.springframework.core.DefaultParameterNameDiscoverer
DefaultParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer();
String[] params = discoverer.getParameterNames(MathUtils.class.getMethod("isPrime", Integer.class));
ใช่.
โค้ดต้องคอมไพเลอร์ด้วยคอมไพเลอร์ที่เข้ากันได้กับ Java 8พร้อมอ็อพชันในการจัดเก็บชื่อพารามิเตอร์ที่เป็นทางการที่เปิดอยู่ ( อ็อพชัน-parameters )
จากนั้นข้อมูลโค้ดนี้ควรใช้งานได้:
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());
}
}
คุณสามารถดึงวิธีการด้วยการสะท้อนและตรวจจับประเภทอาร์กิวเมนต์ได้ ตรวจสอบhttp://java.sun.com/j2se/1.4.2/docs/api/java/lang/reflect/Method.html#getParameterTypes%28%29
อย่างไรก็ตามคุณไม่สามารถบอกชื่อของอาร์กิวเมนต์ที่ใช้
เป็นไปได้และ Spring MVC 3 ก็ทำได้ แต่ฉันไม่ได้ใช้เวลาเพื่อดูว่าเป็นอย่างไร
การจับคู่ชื่อพารามิเตอร์เมธอดกับชื่อตัวแปรเทมเพลต URI สามารถทำได้ก็ต่อเมื่อโค้ดของคุณถูกคอมไพล์โดยเปิดใช้งานการดีบัก หากคุณไม่ได้เปิดใช้งานการดีบักคุณต้องระบุชื่อของชื่อตัวแปร URI Template ในคำอธิบายประกอบ @PathVariable เพื่อผูกค่าที่แก้ไขแล้วของชื่อตัวแปรกับพารามิเตอร์เมธอด ตัวอย่างเช่น:
นำมาจากเอกสารสปริง
แม้ว่าจะเป็นไปไม่ได้ (ตามที่คนอื่นแสดงให้เห็น) แต่คุณสามารถใช้คำอธิบายประกอบเพื่อนำไปใช้กับชื่อพารามิเตอร์และรับสิ่งนั้นแม้ว่าจะสะท้อนกลับ
ไม่ใช่วิธีการแก้ปัญหาที่สะอาดที่สุด แต่จะทำให้งานลุล่วง เว็บไซต์บริการบางอย่างทำเช่นนี้เพื่อเก็บชื่อพารามิเตอร์ไว้ (เช่นการปรับใช้ WSs กับ glassfish)
ดูjava.beans.ConstructorPropertiesมันเป็นคำอธิบายประกอบที่ออกแบบมาเพื่อทำสิ่งนี้
ดังนั้นคุณควรจะทำได้:
Whatever.declaredMethods
.find { it.name == 'aMethod' }
.parameters
.collect { "$it.type : $it.name" }
แต่คุณอาจได้รับรายชื่อดังนี้:
["int : arg0"]
ฉันเชื่อว่าสิ่งนี้จะได้รับการแก้ไขใน Groovy 2.5+
ดังนั้นในปัจจุบันคำตอบคือ:
ดูสิ่งนี้ด้วย:
สำหรับทุกวิธีแล้วสิ่งที่ต้องการ:
Whatever.declaredMethods
.findAll { !it.synthetic }
.collect { method ->
println method
method.name + " -> " + method.parameters.collect { "[$it.type : $it.name]" }.join(';')
}
.each {
println it
}
aMethod
ฉันยังไม่ต้องการที่จะระบุชื่อของวิธีการที่ ฉันต้องการรับมันสำหรับทุกวิธีในชั้นเรียน
antlr
ชื่อพารามิเตอร์สำหรับสิ่งนี้ได้หรือไม่?
ตามที่ @Bozho ระบุไว้เป็นไปได้ที่จะดำเนินการดังกล่าวหากมีการรวมข้อมูลการแก้ปัญหาในระหว่างการรวบรวม มีคำตอบที่ดีที่นี่ ...
จะรับชื่อพารามิเตอร์ของตัวสร้างวัตถุ (การสะท้อน) ได้อย่างไร? โดย @AdamPaynter
... โดยใช้ไลบรารี ASM ฉันรวบรวมตัวอย่างที่แสดงให้เห็นว่าคุณจะบรรลุเป้าหมายได้อย่างไร
ก่อนอื่นเริ่มต้นด้วย pom.xml ด้วยการอ้างอิงเหล่านี้
<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>
จากนั้นชั้นเรียนนี้ควรทำในสิ่งที่คุณต้องการ 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;
}
}
นี่คือตัวอย่างของการทดสอบหน่วย
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) {
}
}
}
คุณสามารถดูตัวอย่างทั้งหมดได้ในGitHub
static
เมธอด เนื่องจากในกรณีนี้จำนวนอาร์กิวเมนต์ที่ ASM ส่งคืนแตกต่างกัน แต่เป็นสิ่งที่แก้ไขได้ง่ายชื่อพารามิเตอร์มีประโยชน์กับคอมไพเลอร์เท่านั้น เมื่อคอมไพลเลอร์สร้างไฟล์คลาสจะไม่มีการรวมชื่อพารามิเตอร์ - รายการอาร์กิวเมนต์ของเมธอดประกอบด้วยจำนวนและประเภทของอาร์กิวเมนต์เท่านั้น ดังนั้นจึงเป็นไปไม่ได้ที่จะดึงชื่อพารามิเตอร์โดยใช้การสะท้อนกลับ (ตามที่ติดแท็กในคำถามของคุณ) - ไม่มีอยู่ที่ใดเลย
อย่างไรก็ตามหากการใช้การสะท้อนกลับไม่ใช่ข้อกำหนดที่ยากคุณสามารถดึงข้อมูลนี้ได้โดยตรงจากซอร์สโค้ด (สมมติว่าคุณมี)
Parameter names are only useful to the compiler.
ไม่ถูกต้อง. ดูที่ Retrofit library ใช้อินเทอร์เฟซแบบไดนามิกเพื่อสร้างคำขอ REST API คุณสมบัติอย่างหนึ่งคือความสามารถในการกำหนดชื่อตัวยึดตำแหน่งในเส้นทาง URL และแทนที่ตัวยึดตำแหน่งเหล่านั้นด้วยชื่อพารามิเตอร์ที่เกี่ยวข้อง
เพื่อเพิ่ม 2 เซ็นต์ของฉัน ข้อมูลพารามิเตอร์มีอยู่ในไฟล์คลาส "สำหรับการดีบัก" เมื่อคุณใช้ javac -g เพื่อคอมไพล์ซอร์ส และสามารถใช้ได้กับ APT แต่คุณจะต้องมีคำอธิบายประกอบจึงไม่มีประโยชน์สำหรับคุณ (มีคนพูดถึงสิ่งที่คล้ายกันเมื่อ 4-5 ปีก่อนที่นี่: http://forums.java.net/jive/thread.jspa?messageID=13467&tstart=0 )
โดยรวมแล้วคุณจะไม่สามารถรับมันได้เว้นแต่คุณจะทำงานกับไฟล์ต้นฉบับโดยตรง (คล้ายกับที่ APT ทำในเวลาคอมไพล์)