Это не имеет ничего общего с макросами и все, что связано с системой отражения времени выполнения Scala. Короче говоря, и Java 8, и Scala 2.11 хотели иметь возможность искать имена параметров, и каждая реализовала для этого свою систему отражения.
Это отлично работает, если все написано на Scala, и вы скомпилируете его вместе (да!). Проблемы возникают, когда у вас есть класс 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. Он генерирует имена x$1
, x$2
, ... в качестве заполнителей, так же, как отражение в Java 8 генерирует имена arg0
, arg1
, ... в качестве заполнителей, когда мы проверяли скомпилированный типаж Scala. (И если бы мы не прошли -parameters
, он сгенерировал бы эти имена даже для MyInterface.java
.)
Решение
Лучшее решение (которое работает в 2.11), которое я могу придумать, чтобы получить имена параметров класса Java, - это использовать отражение Java из Scala. Что-то вроде
$ 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.
Все хорошо, что хорошо кончается?
person
Alec
schedule
03.08.2016