这与宏无关,与 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$2
Java 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 类。
一切都好,结局好?