क्या मैं जावा प्रतिबिंब का उपयोग करके विधि पैरामीटर नाम प्राप्त कर सकता हूं?


124

अगर मेरे पास इस तरह एक वर्ग है:

public class Whatever
{
  public void aMethod(int aParam);
}

क्या यह जानने का कोई तरीका है कि aMethodनामित पैरामीटर का उपयोग किया aParamजाता है, जो कि प्रकार का है int?


10
7 जवाब और किसी ने भी "विधि हस्ताक्षर" शब्द का उल्लेख नहीं किया। यह मूल रूप से है जो आप प्रतिबिंब के साथ आत्मनिरीक्षण कर सकते हैं, जिसका अर्थ है: कोई पैरामीटर नाम नहीं।
ewernli

3
यह है संभव, नीचे मेरा उत्तर देख
flybywire

4
6 साल बाद, यह अब जावा 8 में प्रतिबिंब के माध्यम से संभव है , इस एसओ उत्तर को
इयरकैम

जवाबों:


90

संक्षेप में:

  • पैरामीटर नाम हो रही है यदि संभव हो तो डिबग जानकारी संकलन के दौरान शामिल किया गया है। देखें इस उत्तर अधिक जानकारी के लिए
  • अन्यथा पैरामीटर नाम प्राप्त करना संभव नहीं है
  • पैरामीटर प्रकार का उपयोग करना संभव है method.getParameterTypes()

एक संपादक के लिए स्वत: पूर्ण कार्यक्षमता लिखने के लिए (जैसा कि आप टिप्पणियों में से एक में कहा गया है) कुछ विकल्प हैं:

  • उपयोग arg0, arg1, arg2आदि
  • उपयोग intParam, stringParam, objectTypeParam, आदि
  • उपरोक्त के संयोजन का उपयोग करें - गैर-आदिम प्रकारों के लिए पूर्व, और बाद में आदिम प्रकारों के लिए।
  • सभी पर तर्क नाम न दिखाएं - केवल प्रकार।

4
क्या यह इंटरफेसेस के साथ संभव है? मुझे अभी भी इंटरफ़ेस पैरामीटर नाम प्राप्त करने का कोई तरीका नहीं मिला।
Cemo

@Cemo: क्या आप इंटरफ़ेस पैरामीटर नाम प्राप्त करने का एक तरीका खोजने में सक्षम थे?
वैभव गुप्ता

बस जोड़ने के लिए, यही कारण है कि वसंत को आदिम प्रकारों से ऑब्जेक्ट को अस्पष्ट रूप से बनाने के लिए @ConstructorProperties एनोटेशन की आवश्यकता है।
भारत

102

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अपने जावा 8 कंपाइलर से तर्क पारित करना है तो प्रिंट करना चाहिए ।

मावेन उपयोगकर्ताओं के लिए:

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

अधिक जानकारी के लिए निम्न लिंक देखें:

  1. आधिकारिक जावा ट्यूटोरियल: विधि पैरामीटर के नाम प्राप्त करना
  2. JEP 118: रनटाइम में पैरामीटर नामों तक पहुंच
  3. पैरामीटर वर्ग के लिए Javadoc

2
मुझे नहीं पता कि उन्होंने कंपाइलर प्लगइन के लिए तर्कों को बदल दिया है, लेकिन नवीनतम (अब के 3.5.1) के साथ मुझे कॉन्फ़िगरेशन अनुभाग में कंपाइलर args का उपयोग करना था: <कॉन्फ़िगरेशन> <compilerArgs> <arg> - पैरामीटर </ arg> </ compilerArgs> </ कॉन्फ़िगरेशन>
अधिकतम

15

इसी समस्या के समाधान के लिए परनामी पुस्तकालय बनाया गया।

यह कुछ अलग तरीकों से विधि के नाम निर्धारित करने की कोशिश करता है। यदि कक्षा को डिबगिंग के साथ संकलित किया गया था तो यह कक्षा के बायोटेक को पढ़कर जानकारी निकाल सकता है।

एक और तरीका यह है कि इसे संकलित किए जाने के बाद एक निजी स्थिर सदस्य को कक्षा के बाइटकोड में इंजेक्ट किया जाए, लेकिन इससे पहले कि इसे जार में रखा जाए। यह तब रनटाइम पर कक्षा से इस जानकारी को निकालने के लिए प्रतिबिंब का उपयोग करता है।

https://github.com/paul-hammant/paranamer

मुझे इस लाइब्रेरी का उपयोग करने में समस्या थी, लेकिन मैंने इसे अंत में काम कर लिया। मैं अनुचर को समस्याओं की रिपोर्ट करने की उम्मीद कर रहा हूं।


मैं एक Android APK के अंदर paranamer का उपयोग करने की कोशिश कर रहा हूं। लेकिन मुझे मिल रहा हैParameterNAmesNotFoundException
रिलवान

10

org.springframework.core.efaultParameterNameDiscoverer वर्ग देखें

DefaultParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer();
String[] params = discoverer.getParameterNames(MathUtils.class.getMethod("isPrime", Integer.class));

10

हाँ।
कोड को औपचारिक पैरामीटर नामों को चालू करने के लिए विकल्प ( -पारमेटर्स विकल्प) के साथ जावा 8 अनुरूप संकलक के साथ संकलित किया जाना चाहिए
फिर यह कोड स्निपेट काम करना चाहिए:

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

कोशिश की और यह काम किया! एक टिप हालांकि, मुझे इन संकलक विन्यासों के प्रभावी होने के लिए आपकी परियोजना को फिर से बनाना पड़ा।
ishmelMakitla

5

आप प्रतिबिंब के साथ विधि को पुनः प्राप्त कर सकते हैं और पता लगा सकते हैं कि यह तर्क प्रकार है। चेक http://java.sun.com/j2se/1.4.2/docs/api/java/lang/reflect/Method.html#getParameterTypes%28%29

हालाँकि, आप उपयोग किए गए तर्क का नाम नहीं बता सकते।


17
सचमुच यह सब संभव है। हालांकि उनका सवाल स्पष्ट रूप से पैरामीटर नाम के बारे में था । टोपिकिटेल की जाँच करें।
BalusC

1
और मैं उद्धृत करता हूं: "हालांकि, आप उपयोग किए गए तर्क का नाम नहीं बता सकते।" बस मेरा जवाब -_-
जॉन्को

3
इस प्रश्न का प्रारूपण उस तरह से नहीं किया गया, जैसा कि वह टाइप प्राप्त करने के बारे में नहीं जानता था।
बालूसी

3

यह संभव है और स्प्रिंग एमवीसी 3 इसे करता है, लेकिन मैंने यह देखने के लिए समय नहीं लिया कि कैसे।

URI टेम्प्लेट चर नामों के लिए विधि पैरामीटर नामों का मिलान केवल तभी किया जा सकता है जब आपका कोड डीबगिंग सक्षम हो। यदि आपके पास डिबगिंग सक्षम नहीं है, तो आपको @PathVariable एनोटेशन में URI टेम्प्लेट चर नाम का नाम निर्दिष्ट करना होगा ताकि वैरिएबल पैरामीटर के लिए वैरिएबल मान को एक विधि पैरामीटर में बाँध सकें। उदाहरण के लिए:

वसंत प्रलेखन से लिया गया


org.springframework.core.Conventions.getVariableName () लेकिन मुझे लगता है कि आपके पास निर्भरता के रूप में cglib होना चाहिए
Radu Toader

3

हालांकि यह संभव नहीं है (जैसा कि दूसरों ने सचित्र किया है), आप कर सकते थे पैरामीटर नाम पर ले जाने के लिए एक एनोटेशन का उपयोग हैं, और उस प्रतिबिंब को प्राप्त कर सकते हैं।

सबसे साफ समाधान नहीं है, लेकिन यह काम हो जाता है। कुछ webservices वास्तव में पैरामीटर नाम रखने के लिए ऐसा करते हैं (जैसे: ग्लासफिश के साथ WSs को तैनात करना)।



3

तो आपको ऐसा करने में सक्षम होना चाहिए:

Whatever.declaredMethods
        .find { it.name == 'aMethod' }
        .parameters
        .collect { "$it.type : $it.name" }

लेकिन आपको शायद एक सूची मिल जाएगी जैसे:

["int : arg0"]

मेरा मानना ​​है कि यह ग्रूवी 2.5+ में तय किया जाएगा

तो वर्तमान में, उत्तर है:

  • यदि यह ग्रूवी वर्ग है, तो नहीं, आपको नाम नहीं मिल सकता है, लेकिन आपको भविष्य में सक्षम होना चाहिए।
  • यदि यह जावा 8 के तहत संकलित एक जावा वर्ग है, तो आपको सक्षम होना चाहिए।

यह सभी देखें:


हर विधि के लिए, फिर कुछ इस तरह:

Whatever.declaredMethods
        .findAll { !it.synthetic }
        .collect { method -> 
            println method
            method.name + " -> " + method.parameters.collect { "[$it.type : $it.name]" }.join(';')
        }
        .each {
            println it
        }

मैं एक विधि का नाम भी नहीं बताना चाहता aMethod। मैं एक कक्षा में सभी तरीकों के लिए इसे प्राप्त करना चाहता हूं।
स्नूप

@snoop ने हर गैर-सिंथेटिक पद्धति को प्राप्त करने का एक उदाहरण जोड़ा
बजे

क्या हम इसके antlrलिए पैरामीटर नाम प्राप्त करने के लिए उपयोग कर सकते हैं?
स्नूप

2

यदि आप ग्रहण का उपयोग करते हैं, तो कंपाइलर को विधि पैरामीटर के बारे में जानकारी संग्रहीत करने की अनुमति देने के लिए bellow छवि देखें

यहाँ छवि विवरण दर्ज करें


2

जैसा कि @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 पर पा सकते हैं

चेतावनियां

  • मैंने तरीकों के लिए काम करने के लिए @AdamPaynter से मूल समाधान को थोड़ा बदल दिया। अगर मुझे ठीक से समझा जाए, तो उसका समाधान केवल कंस्ट्रक्टरों के साथ काम करता है।
  • यह समाधान staticविधियों के साथ काम नहीं करता है । यह इस मामले में कारण है कि ASM द्वारा लौटाए गए तर्कों की संख्या अलग है, लेकिन यह कुछ ऐसा है जिसे आसानी से तय किया जा सकता है।

0

पैरामीटर नाम केवल संकलक के लिए उपयोगी हैं। जब कंपाइलर एक क्लास फ़ाइल बनाता है, तो पैरामीटर नाम शामिल नहीं होते हैं - एक विधि की तर्क सूची में केवल संख्या और इसके तर्कों के प्रकार शामिल होते हैं। इसलिए प्रतिबिंब का उपयोग करके पैरामीटर नाम को पुनः प्राप्त करना असंभव होगा (जैसा कि आपके प्रश्न में टैग किया गया है) - यह कहीं भी मौजूद नहीं है।

हालांकि, यदि प्रतिबिंब का उपयोग एक कठिन आवश्यकता नहीं है, तो आप इस जानकारी को सीधे स्रोत कोड से प्राप्त कर सकते हैं (यह मानते हुए कि आपके पास है)।


2
पैरामीटर नाम न केवल एक कंपाइलर के लिए उपयोगी है, वे एक परिवादी के उपभोक्ता को भी जानकारी देते हैं, मान लें कि मैंने एक क्लास स्ट्रेंजडेट लिखा था जिसमें विधि जोड़ने (इंट दिन, इंट घंटे, इंट मिनट) था यदि आप इस का उपयोग करने के लिए गए और एक विधि मिली। add (int arg0, int arg1, int arg2) यह कितना उपयोगी होगा?
डेविड वाटर्स

मुझे खेद है - आपने मेरे उत्तर को पूरी तरह गलत समझा। कृपया इसे प्रश्न के संदर्भ में फिर से लिखें।
डैनबेन

2
मापदंडों के नामों को प्राप्त करना उस पद्धति को प्रतिबिंबित करने के लिए एक महान लाभ है। यह केवल संकलक के लिए उपयोगी नहीं है, यहां तक ​​कि प्रश्न के संदर्भ में भी।
corsiKa

Parameter names are only useful to the compiler.गलत। रेट्रोफिट लाइब्रेरी देखें। यह REST API अनुरोध बनाने के लिए डायनेमिक इंटरफेस का उपयोग करता है। इसकी एक विशेषता URL पथों में प्लेसहोल्डर नामों को परिभाषित करने और उन प्लेसहोल्डर्स को उनके संबंधित पैरामीटर नामों के साथ बदलने की क्षमता है।
२०:५h पर atealChx101

0

मेरे 2 सेंट जोड़ने के लिए; जब आप स्रोत को संकलित करने के लिए javac -g का उपयोग करते हैं तो पैरामीटर जानकारी "डिबगिंग के लिए" एक वर्ग फ़ाइल में उपलब्ध है। और यह एपीटी के लिए उपलब्ध है, लेकिन आपको एनोटेशन की आवश्यकता होगी ताकि आपको कोई फायदा न हो। (किसी ने 4-5 साल पहले कुछ इसी तरह की चर्चा की थी: http://forums.java.net/jive/thread.jspa?messageID=13467&tstart=0 )

जब तक आप सीधे स्रोत फ़ाइलों पर काम नहीं करते हैं, कुल मिलाकर इन-शॉर्ट आपको नहीं मिल सकता है (एपीटी संकलन के समय जैसा होता है)।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.