Javaリフレクションを使用してメソッドパラメータ名を取得できますか?


124

このようなクラスがある場合:

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

それを知るためにどのような方法があるaMethodという名前のパラメータ使用するaParamタイプのものであり、int


10
7つの回答があり、「メソッド署名」という用語について誰も言及していません。これは、基本的に、リフレクションでイントロスペクトできるものです。つまり、パラメーター名がないということです。
ewernli

3
それがある可能、下記の私の答えを参照
フライ・バイ・ワイヤ

4
6年後、Java 8でのリフレクションによって可能になりました。このSOの回答を
イヤーカム'28年

回答:


90

要約する:

  • コンパイル中にデバッグ情報が含まれている場合、パラメーター名を取得できます。詳細については、この回答を参照してください
  • そうでない場合、パラメータ名を取得することはできません
  • パラメータタイプの取得が可能です。 method.getParameterTypes()

エディターのオートコンプリート機能を作成するために(コメントの1つで述べたように)、いくつかのオプションがあります。

  • 使用arg0arg1arg2など
  • 使用intParamstringParamobjectTypeParam、など
  • 上記の組み合わせを使用します。前者は非プリミティブ型、後者はプリミティブ型です。
  • 引数名はまったく表示せず、タイプのみを表示します。

4
これはインターフェイスで可能ですか?それでも、インターフェイスパラメータ名を取得する方法が見つかりませんでした。
Cemo 2014

@Cemo:インターフェイスパラメータ名を取得する方法を見つけることができましたか?
Vaibhav Gupta

追加するだけで、プリミティブ型からオブジェクトを明確に作成するために、Springには@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;
            }
        }
    }
}

これは印刷する必要があり[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>

詳細については、次のリンクを参照してください。

  1. 公式Javaチュートリアル:メソッドパラメータの名前の取得
  2. JEP 118:実行時のパラメーター名へのアクセス
  3. ParameterクラスのJavadoc

2
コンパイラプラグインの引数が変更されたかどうかはわかりませんが、最新(現在は3.5.1)では、構成セクションでコンパイラの引数を使用する必要がありました:<configuration> <compilerArgs> <arg>-パラメータ</ arg> </ compilerArgs> </ configuration>
最大

15

Paranamerライブラリは、この同じ問題を解決するために作成されました。

それはいくつかの異なる方法でメソッド名を決定しようとします。クラスがデバッグでコンパイルされている場合、クラスのバイトコードを読み取ることで情報を抽出できます。

別の方法は、クラスのコンパイル後、jarに配置される前に、クラスのバイトコードにプライベート静的メンバーを挿入することです。次に、リフレクションを使用して、実行時にクラスからこの情報を抽出します。

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

このライブラリの使用に問題がありましたが、最終的には動作しました。問題をメンテナに報告したいと思っています。


私はAndroid APK内でパラナマーを使用しようとしています。しかし、私は得ていますParameterNAmesNotFoundException
Rilwan

10

org.springframework.core.DefaultParameterNameDiscovererクラスを参照してください

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

10

はい。正式なパラメーター名を保存するオプション(-parametersオプション)を使用して
コードをJava 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());
   }
}

これを試してみましたが、うまくいきました!ただし、これらのコンパイラ構成を有効にするには、プロジェクトを再構築する必要がありました。
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が必要だと思います
Radu Toader

3

(他の人が説明したように)不可能です、アノテーションを使用してパラメーター名を引き継ぎ、リフレクションを介してそれを取得できます。

最もクリーンなソリューションではありませんが、それで仕事が完了します。一部のWebサービスは、実際にパラメーター名を保持するためにこれを実行します(つまり、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このためのパラメーター名を取得するために使用することはできませんか?
スヌープ

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 minute)を持つクラス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要求を作成します。その機能の1つは、URLパスでプレースホルダー名を定義し、それらのプレースホルダーを対応するパラメーター名で置き換える機能です。
TheRealChx101

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.