【问题标题】:How to get parameter names and types via reflection in Scala/Java methods?如何通过 Scala/Java 方法中的反射获取参数名称和类型?
【发布时间】:2011-11-30 05:39:15
【问题描述】:

我们可以使用反射来获取方法名,如下:

object Foo { def bar(name:String, age:Int) = {} } 
val foo = Foo.getClass
val methods = foo.getMethods.filter(_.getName.startsWith("b"))
methods.foreach(m => println(m.getName))

我现在需要获取参数类型和名称。

  • 参数名称是否存储在字节码中?如果答案是肯定的,如何访问它们?
  • 如果上面的答案是否定的,我们可以使用注释以某种方式存储名称吗?
  • 谁能给出一个例子来阅读这些类型,以及如何使用它们。我只对具有String 和/或Array[String] 类型参数的函数感兴趣。

[EDIT:] Java 版本的解决方案也可以。

[编辑:] 注释似乎是一种方法。但是,Scala 注释支持并不是那么好。 Related SO question.

【问题讨论】:

  • 对于 (1),我相信答案是否定的,因为 Java 类文件不存储名称。 (2) 似乎可能,但我不知道。对于 (3),请记住实际上不是整个类型,只是存储类型构造函数,因为擦除。再次注释可能会解决这个问题,但尝试不使用反射的系统可能会更好?根据我的经验,虽然反射有时很方便,但你最终会后悔的(而且现在它甚至听起来都不方便)。
  • 名称似乎存储在字节码中,因为当我使用 Java 中的 Scala jar(没有 Scala 源代码)时,Eclipse/Netbeans IDE 代码完成会显示名称。
  • @Jus12 不过,这不是字节码的要求,可能是由于存在调试符号。 javap -l 将打印出一个符号表(如果存在);您可以通过测试javajava -g:vars 来验证差异,以查看带有和不带有符号名称信息的类文件。
  • 我出于其他原因创建它,但它也涵盖了问题:gist.github.com/1257784 应该注意的是,它仅适用于 Scala 编译器的主干版本。

标签: java scala reflection


【解决方案1】:

我没有尝试过,但是http://paranamer.codehaus.org/ 是为这个任务设计的。

【讨论】:

  • 我喜欢这个!都是自成体系的,不依赖asm;它只复制需要的部分。
【解决方案2】:

Java 的字节码规范不要求存储参数名称。但是,它们可以通过调试符号潜入(如果编译器被告知生成它们)。我知道 ASM 字节码库会读取这些符号(如果存在)。有关查找构造函数参数名称的 Java 示例,请参阅 my answer to "How to get the parameter names of an object's constructors"(在字节码中,构造函数只是名称为 <init> 的方法)。

【讨论】:

  • 谢谢。我调整了您的答案以使其与 Scala 一起使用。只需要弄清楚如何处理Type 类。
  • @Jus12:Type 类有什么问题?
  • 这是我的错误。我使用的是 Java Type,而我应该使用库的 Type 类。我解决了。
【解决方案3】:

如果类中存在调试信息,可以按如下方式进行。

我基本上使用Adam Paynter's answer 并在稍微编辑后从here 复制粘贴代码以使其在Scala 中工作。

package test
import java.io.InputStream
import java.util.ArrayList
import scala.collection.JavaConversions._
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

object Util {
  case class Param(paraName:String, paraType:Type)
  case class ScalaMethod(name:String, returnType:Type, params:List[Param])

  def main(args:Array[String]):Unit = {
    getMethods(scala.util.Random.getClass).foreach(printMethod _ )
    def printMethod(m:ScalaMethod) = {
      println (m.name+" => "+m.returnType.getClassName)
      m.params.foreach(p =>
        println (" "+ p.paraName+":"+p.paraType.getClassName))
    }
  }

  /**
   * extracts the names, parameter names and parameter types of all methods of c
   */
  def getMethods(c:Class[_]):List[ScalaMethod] = {
    val cl:ClassLoader = c.getClassLoader();
    val t:Type = Type.getType(c);
    val url:String = t.getInternalName() + ".class";
    val is:InputStream = cl.getResourceAsStream(url);
    if (is == null)
      throw new IllegalArgumentException("""The class loader cannot
                                         find the bytecode that defined the
                                         class (URL: " + url + ")""");
    val cn = new ClassNode();
    val cr = new ClassReader(is);
    cr.accept(cn, 0);
    is.close();
    val methods = cn.methods.asInstanceOf[java.util.List[MethodNode]];
    var mList:List[ScalaMethod] = Nil
    if (methods.size > 0) for (i <- 1 to methods.size) {
      val m:MethodNode = methods.get(i-1)
      val argTypes:Array[Type] = Type.getArgumentTypes(m.desc);
      val paraNames = new java.util.ArrayList[String](argTypes.length)
      val vars = m.localVariables.asInstanceOf[java.util.List[LocalVariableNode]];
      var pList:List[Param] = Nil
      if (argTypes.length > 0) for (i <- 0 to argTypes.length) {
          // The first local variable actually represents the "this" object
          paraNames.add(vars.get(i).name);
          pList = Param(paraNames.get(i-1), argTypes(i-1)) :: pList
      }
      mList = ScalaMethod(m.name, Type.getReturnType(m.desc), pList) :: mList
    }
    mList
  }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-11-06
    • 2017-04-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多