web-dev-qa-db-ja.com

リフレクションを使用してJava 8でメソッドパラメータ名を取得する方法は?

Java 8には、Reflection APIを使用してメソッドパラメーター名を取得する機能があります。

  1. これらのメソッドパラメータ名を取得するにはどうすればよいですか?

  2. 私の知る限り、クラスファイルには正式なパラメータ名は格納されていません。リフレクションを使用してこれらを取得するにはどうすればよいですか?

32
Prateek

これらのメソッドパラメータ名を取得するにはどうすればよいですか?

基本的に、次のことが必要です。

  • Classへの参照を取得します
  • Classから、 getDeclaredMethod() またはgetDeclaredMethods()を呼び出してMethodオブジェクトへの参照を返すことにより、Methodへの参照を取得します。
  • Methodオブジェクトから、Parameterオブジェクトの配列を返す(Java 8)の新しい)getParameters()を呼び出します
  • Parameterオブジェクトで、getName()を呼び出します
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());
   }
}

出力:

...
indexOf
  arg0
indexOf
  arg0
  arg1
...

また、私の知る限りでは、.classファイルには仮パラメータが格納されていません。次に、リフレクションを使用してそれらを取得するにはどうすればよいですか?

Parameter.getName() のjavadocを参照してください:

...パラメータの名前が存在する場合、このメソッドはクラスファイルによって提供される名前を返しますそれ以外の場合、このメソッドはargNの形式の名前を合成します。Nは、パラメーターを宣言するメソッドの記述子内のパラメーターのインデックスです。 。

JDKがこれをサポートするかどうかは実装固有です(上記の出力からわかるように、JDK 8のビルド125はサポートしていません)。クラスファイル形式は、特定のJVM/javac実装で使用でき、それをサポートしない他の実装では無視されるオプションの属性をサポートします。

上記の出力は、arg0arg1、...、pre Java 8 JVMs-知っている必要があるのは、Method.getParameterTypes()を介してアクセス可能なパラメータ数だけです。 :

Class<String> clz = String.class;
for (Method m : clz.getDeclaredMethods()) {
  System.err.println(m.getName());
  int paramCount = m.getParameterTypes().length;
  for (int i = 0;  i < paramCount;  i++) {
    System.err.println("  arg" + i);
  }
}

JDK 8の新しい点は、拡張APIがあり、JVMがarg0arg1の代わりに実際のパラメーター名を提供するpossibilityであるということです。 ...

このようなオプション機能のサポートは、optional attributesを通じて可能です。これは、さまざまなクラスファイル構造に添付できます。クラスファイル内のmethod_info構造体については、 4.6。メソッド を参照してください。 JVM仕様の 4.7.1。新しい属性の定義と命名 も参照してください。

JDK 8では、クラスファイルのバージョンが52に増加するため、この機能をサポートするためにファイル形式自体を変更することもできます。

JEP 118:実行時のパラメーター名へのアクセス も参照してください。提案されている実装モデルは、パラメータ名を格納するオプションの属性を追加することです。クラスファイル形式はすでにこれらのオプション属性をサポートしているため、クラスファイルが仕様で要求されているように単純に無視される古いJVMで引き続き使用できるように、これも可能です。

Java Virtual Machineの実装は、認識しない属性を静かに無視する必要があります。

更新

@assyliasで示唆されているように、パラメータ名を反映するためのメタデータをクラスファイルに追加するには、ソースをjavacコマンドラインオプション-parametersでコンパイルする必要があります。ただし、これはもちろんこのオプションでコンパイルされたコードにのみ影響します-ランタイムライブラリはこのフラグでコンパイルされず、したがってクラスファイルに必要なエントリが含まれていないため、上記のコードはarg0arg1などを出力します。

46
Andreas Fester

Andreasに感謝しますが、最終的に Method Parameters のOracleチュートリアルから完全なソリューションを得ました。

それは言います、

Java.lang.reflect.Executable.getParametersメソッドを使用して、任意のメソッドまたはコンストラクターの仮パラメーターの名前を取得できます。 (クラスMethodおよびConstructorは、クラスExecutableを拡張するため、メソッドExecutable.getParametersを継承します。)ただし、.classファイルは、デフォルトでは正式なパラメーター名を格納しません。これは、クラスファイルを生成および使用する多くのツールが、パラメータ名を含む.classファイルのより大きな静的および動的なフットプリントを期待しないためです。特に、これらのツールはより大きな.classファイルを処理する必要があり、Java仮想マシン(JVM)はより多くのメモリを使用します。さらに、シークレットやパスワードなどの一部のパラメーター名は、セキュリティに敏感なメソッドに関する情報を公開します。

特定の.classファイルに仮パラメータ名を保存し、Reflection APIが仮パラメータ名を取得できるようにするには、javacコンパイラの-parametersオプションを使用してソースファイルをコンパイルします

コンパイル方法

Remember to compile the with the -parameters compiler option

期待される出力(完全な例については、上記のリンクをご覧ください)

Java MethodParameterSpy ExampleMethods

このコマンドは次を出力します。

Number of constructors: 1

Constructor #1
public ExampleMethods()

Number of declared constructors: 1

Declared constructor #1
public ExampleMethods()

Number of methods: 4

Method #1
public boolean ExampleMethods.simpleMethod(Java.lang.String,int)
             Return type: boolean
     Generic return type: boolean
         Parameter class: class Java.lang.String
          Parameter name: stringParam
               Modifiers: 0
            Is implicit?: false
        Is name present?: true
           Is synthetic?: false
         Parameter class: int
          Parameter name: intParam
               Modifiers: 0
            Is implicit?: false
        Is name present?: true
           Is synthetic?: false

Method #2
public int ExampleMethods.varArgsMethod(Java.lang.String...)
             Return type: int
     Generic return type: int
         Parameter class: class [Ljava.lang.String;
          Parameter name: manyStrings
               Modifiers: 0
            Is implicit?: false
        Is name present?: true
           Is synthetic?: false

Method #3
public boolean ExampleMethods.methodWithList(Java.util.List<Java.lang.String>)
             Return type: boolean
     Generic return type: boolean
         Parameter class: interface Java.util.List
          Parameter name: listParam
               Modifiers: 0
            Is implicit?: false
        Is name present?: true
           Is synthetic?: false

Method #4
public <T> void ExampleMethods.genericMethod(T[],Java.util.Collection<T>)
             Return type: void
     Generic return type: void
         Parameter class: class [Ljava.lang.Object;
          Parameter name: a
               Modifiers: 0
            Is implicit?: false
        Is name present?: true
           Is synthetic?: false
         Parameter class: interface Java.util.Collection
          Parameter name: c
               Modifiers: 0
            Is implicit?: false
        Is name present?: true
           Is synthetic?: false
14
Prateek

intellij 13のメソッドパラメーターに関する情報(リフレクション経由で使用可能)を保存する 、Eclipse内の「javac -parameters」に相当するIDEは、メソッドに関する情報を保存するパラメータ(リフレクション経由で使用可能) 'ウィンドウ->設定-> Java->コンパイラ。

6
vorburger

Paranamer libを使用できます( https://github.com/paul-hammant/paranamer

私のために働くサンプルコード:

import com.thoughtworks.paranamer.AnnotationParanamer;
import com.thoughtworks.paranamer.BytecodeReadingParanamer;
import com.thoughtworks.paranamer.CachingParanamer;
import com.thoughtworks.paranamer.Paranamer;

Paranamer info = new CachingParanamer(new AnnotationParanamer(new BytecodeReadingParanamer()));

Method method = Foo.class.getMethod(...);
String[] parameterNames = info.lookupParameterNames(method);

Mavenを使用する場合、pom.xmlにこの依存関係を追加します。

<dependency>
    <groupId>com.thoughtworks.paranamer</groupId>
    <artifactId>paranamer</artifactId>
    <version>2.8</version>
</dependency>
5
Gustavo Leitão