Java 리플렉션을 사용하여 메소드 매개 변수 이름을 얻을 수 있습니까?


124

다음과 같은 수업이있는 경우 :

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

유형 aMethod의 매개 변수 를 사용하는 것을 알 수있는 방법이 있습니까?aParamint


10
7 개의 답변이 있었고 아무도 "메소드 서명"이라는 용어를 언급하지 않았습니다. 이것은 기본적으로 리플렉션으로 인트로 스펙트 할 수있는 것입니다. 즉, 매개 변수 이름이 없습니다.
ewernli 2010

3
그것은 이다 가능, 내 대답은 아래를 참조
플라이 바이 와이어

4
육년 후, 그것의 반사를 통해 지금 가능 자바 (8) 참조 이 SO 답변
earcam

답변:


90

요약:

  • 매개 변수 이름을 얻는 것은 이다 디버그 정보를 컴파일하는 동안 포함 된 경우 가능합니다. 자세한 내용은 이 답변 을 참조하십시오.
  • 그렇지 않으면 매개 변수 이름을 가져올없습니다.
  • 매개 변수 유형을 가져올 수 있습니다. method.getParameterTypes()

(주석 중 하나에서 언급했듯이) 편집기의 자동 완성 기능을 작성하기 위해 몇 가지 옵션이 있습니다.

  • 사용 arg0, arg1, arg2
  • 사용 intParam, stringParam, objectTypeParam, 등
  • 위의 조합을 사용하십시오. 전자는 기본이 아닌 유형에 대해, 후자는 기본 유형에 대해 사용합니다.
  • 인수 이름을 전혀 표시하지 않고 유형 만 표시합니다.

4
인터페이스로 가능합니까? 여전히 인터페이스 매개 변수 이름을 얻는 방법을 찾을 수 없습니다.
Cemo 2014-04-04

@Cemo : 인터페이스 매개 변수 이름을 얻는 방법을 찾을 수 있었습니까?
Vaibhav Gupta

추가하기 위해 스프링이 기본 유형에서 객체를 모호하지 않게 생성하기 위해 @ConstructorProperties 주석이 필요한 이유입니다.
Bharat

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

Java 8 컴파일러에 인수를 [aParam]전달한 경우 인쇄 해야합니다 -parameters.

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>

자세한 내용은 다음 링크를 참조하십시오.

  1. 공식 자바 튜토리얼 : 메소드 매개 변수의 이름 얻기
  2. JEP 118 : 런타임시 매개 변수 이름에 액세스
  3. 매개 변수 클래스 용 Javadoc

2
그들이 컴파일러 플러그인에 대한 인수를 변경했는지는 모르겠지만 최신 버전 (현재 3.5.1)에서는 구성 섹션에서 컴파일러 인수를 사용해야했습니다. <configuration> <compilerArgs> <arg>- 매개 변수 </ arg> </ compilerArgs> </ configuration>
최대

15

이 같은 문제를 해결하기 위해 Paranamer 라이브러리가 만들어졌습니다.

몇 가지 다른 방법으로 메서드 이름을 결정하려고합니다. 클래스가 디버깅으로 컴파일 된 경우 클래스의 바이트 코드를 읽어 정보를 추출 할 수 있습니다.

또 다른 방법은 컴파일 된 후 jar에 배치되기 전에 전용 정적 멤버를 클래스의 바이트 코드에 삽입하는 것입니다. 그런 다음 리플렉션을 사용하여 런타임에 클래스에서이 정보를 추출합니다.

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

이 라이브러리를 사용하는 데 문제가 있었지만 결국 작동하게되었습니다. 관리자에게 문제를보고하고 싶습니다.


나는 안드로이드 APK 내에서 paranamer를 사용하려고합니다. 그러나 나는 얻고있다ParameterNAmesNotFoundException
Rilwan

10

org.springframework.core.DefaultParameterNameDiscoverer 클래스 참조

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

10

예. 형식 매개 변수 이름을 저장하는 옵션이 설정된 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());
   }
}

이것을 시도하고 작동했습니다! 그러나 한 가지 팁은 이러한 컴파일러 구성이 적용되도록 프로젝트를 다시 빌드해야한다는 것입니다.
ishmaelMakitla

5

리플렉션을 사용하여 메서드를 검색하고 인수 유형을 감지 할 수 있습니다. http://java.sun.com/j2se/1.4.2/docs/api/java/lang/reflect/Method.html#getParameterTypes%28%29 확인

그러나 사용 된 인수의 이름은 말할 수 없습니다.


17
정말 모든 것이 가능합니다. 그의 질문은 그러나이었다 명시 적으로 정보] 매개 변수 이름 . 주제 제목을 확인하십시오.
BalusC 2010

1
그리고 나는 인용한다 : "그러나 당신은 사용 된 논쟁의 이름을 말할 수 없다." 내 대답 -_- 읽기
Johnco

3
질문은 그가 유형을 얻는 것에 대해 알지 못했던 방식으로 공식화되지 않았습니다.
BalusC 2010

3

가능하고 Spring MVC 3가 그것을 수행하지만 정확히 어떻게하는지 시간이 걸리지 않았습니다.

메서드 매개 변수 이름과 URI 템플릿 변수 이름의 일치는 디버깅이 활성화 된 상태로 코드가 컴파일 된 경우에만 수행 할 수 있습니다. 디버깅을 활성화하지 않은 경우 변수 이름의 확인 된 값을 메서드 매개 변수에 바인딩하려면 @PathVariable 주석에 URI 템플릿 변수 이름의 이름을 지정해야합니다. 예를 들면 :

봄 문서 에서 가져옴


org.springframework.core.Conventions.getVariableName ()하지만 난 당신이 종속성으로 CGLIB가 있어야 가정
라두 Toader에게

3

그것이 (다른 사람이 설명대로) 수는 없지만, 당신은 수있는 매개 변수 이름을 이월 주석을 사용하고 반사하지만 그것을 얻을 수 있습니다.

가장 깨끗한 솔루션은 아니지만 작업을 완료합니다. 일부 웹 서비스는 실제로 매개 변수 이름을 유지하기 위해이 작업을 수행합니다 (예 : glassfish로 WS 배포).



3

따라서 다음을 수행 할 수 있어야합니다.

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

그러나 아마도 다음과 같은 목록을 얻을 것입니다.

["int : arg0"]

나는 이것이 Groovy 2.5+에서 수정 될 것이라고 믿습니다.

현재 답은 다음과 같습니다.

  • Groovy 클래스라면 아니요, 이름은 알 수 없지만 나중에는 가능할 것입니다.
  • Java 8에서 컴파일 된 Java 클래스 인 경우 가능합니다.

또한보십시오:


모든 방법에 대해 다음과 같습니다.

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

또한 메서드의 이름을 지정하고 싶지 않습니다 aMethod. 클래스의 모든 메서드에 대해 가져오고 싶습니다.
스눕

@snoop은 모든 비 합성 방법을 얻는 예를 추가했습니다.
tim_yates

antlr이것에 대한 매개 변수 이름을 얻는 데 사용할 수 없습니까?
snoop

2

Eclipse를 사용하는 경우 컴파일러가 메소드 매개 변수에 대한 정보를 저장할 수 있도록 다음 이미지를 참조하십시오.

여기에 이미지 설명 입력


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 day, int hour, int minutes) 메소드가있는 StrangeDate 클래스를 작성했다고 가정합니다. add (int arg0, int arg1, int arg2) 얼마나 유용할까요?
David Waters

미안합니다-당신은 내 대답을 완전히 오해했습니다. 질문의 맥락에서 다시 읽으십시오.
danben

2
매개 변수의 이름을 얻는 것은 해당 메소드를 반사적으로 호출하는 데 큰 이점입니다. 질문의 맥락에서도 컴파일러에게 유용 할뿐만 아니라
corsiKa 2014 년

Parameter names are only useful to the compiler.잘못된. Retrofit 라이브러리를보십시오. 동적 인터페이스를 사용하여 REST API 요청을 생성합니다. 그 기능 중 하나는 URL 경로에 자리 표시 자 이름을 정의하고 해당 자리 표시자를 해당 매개 변수 이름으로 바꾸는 기능입니다.
TheRealChx101 2010 년

0

2 센트를 추가하려면 매개 변수 정보는 javac -g를 사용하여 소스를 컴파일 할 때 "디버깅 용"클래스 파일에서 사용할 수 있습니다. APT에서 사용할 수 있지만 주석이 필요하므로 쓸모가 없습니다. (누군가 4 ~ 5 년 전에 비슷한 것을 논의했습니다. http://forums.java.net/jive/thread.jspa?messageID=13467&tstart=0 )

요약하자면 소스 파일에서 직접 작업하지 않으면 얻을 수 없습니다 (APT가 컴파일 타임에 수행하는 작업과 유사 함).

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.