4

我有一个界面:

trait MyInterface {
  def doSomething(usefulName : Int) : Unit
}

我有一个宏,它迭代接口的方法并使用方法名称和参数进行处理。我通过执行以下操作访问方法名称:

val tpe = typeOf[MyInterface]

// Get lists of parameter names for each method
val listOfParamLists = tpe.decls
  .filter(_.isMethod)
  .map(_.asMethod.paramLists.head.map(sym => sym.asTerm.name))

如果我打印出 的doSomething参数名称,usefulName则变为x$1. 为什么会发生这种情况,有没有办法保留原始参数名称?

我正在使用 scala 版本 2.11.8、宏天堂版本 2.1.0 和黑盒上下文。

该接口实际上是我控制的一个单独的 sbt 项目中的 java 源代码。我试过编译:

javacOptions in (Compile, compile) ++= Seq("-target", "1.8", "-source", "1.8", "-parameters")

参数标志应该保留名称,但我仍然得到与以前相同的结果。

4

1 回答 1

4

这与宏无关,与 Scala 的运行时反射系统有关。简而言之,Java 8 和 Scala 2.11 都希望能够查找参数名称,并且各自实现了自己的反射系统来做到这一点。

如果一切都是 Scala 并且你一起编译它(duh!),这很好用。当您有一个必须单独编译的 Java 类时,就会出现问题。

观察和问题

首先要注意的是,该-parameters标志仅从 Java 8 开始,它与 Scala 2.11 一样古老。所以 Scala 2.11 可能没有使用这个特性来查找方法名......考虑以下

  • MyInterface.java编译javac -parameters MyInterface.java

    public interface MyInterface {
      public int doSomething(int bar);
    }
    
  • MyTrait.scala编译scalac MyTrait.scala

    class MyTrait {
      def doSomething(bar: Int): Int
    }
    

然后,我们可以使用MethodParameterSpy来检查 Java 8-parameter标志应该给我们的参数信息名称。在 Java 编译的界面上运行它,我们得到(这里我缩写了一些输出)

public abstract int MyInterface.doSomething(int)

参数名称:bar

但是在 Scala 编译的类中,我们只得到

public abstract int MyTrait.doSomething(int)

参数名称:arg0

然而,Scala 可以毫无问题地查找自己的参数名称。这告诉我们,Scala 实际上根本不依赖这个 Java 8 特性——它构建了自己的运行时系统来跟踪参数名称。然后,这对于来自 Java 源的类不起作用也就不足为奇了。它生成 names x$1, , ... 作为占位符,就像我们检查编译的 Scala trait 时x$2Java 8 反射生成 names arg0, , ... 作为占位符一样。arg1(如果我们没有通过-parameters,它甚至会为 生成这些名称MyInterface.java。)

解决方案

我能想到的获取 Java 类的参数名称的最佳解决方案(适用于 2.11)是使用 Scala 的 Java 反射。就像是

$ javac -parameters MyInterface.java
$ jar -cf MyInterface.jar MyInterface.class
$ scala -cp MyInterface.jar
scala> :pa
// Entering paste mode (ctrl-D to finish)

import java.lang.reflect._

Class.forName("MyInterface")
  .getDeclaredMethods
  .map(_.getParameters.map(_.getName))

// Exiting paste mode, now interpreting.

res: Array[Array[String]] = Array(Array(bar))

当然,这只有在你有-parameter标志的情况下才有效(否则你得到arg0)。

我可能还应该提到,如果您不知道您的方法是从 Java 还是从 Scala 编译的,您可以随时调用.isJava(例如:),typeOf[MyInterface].decls.filter(_.isMethod).head.isJava然后将其分支到您的初始解决方案或我上面提出的解决方案。

未来

幸运的是,这在 Scala 2.12 中已成为过去。如果我正确阅读了这张票,这意味着在 2.12 中,您的代码将适用于使用编译的 Java 类,-parameter并且我的 Java 反射黑客也适用于 Scala 类。

一切都好,结局好?

于 2016-08-03T09:34:24.910 回答