在Scala中调用私有Java方法

在Scala中调用私有Java方法,java,scala,reflection,Java,Scala,Reflection,我经常使用Scala REPL进行快速Java迭代和测试,但有时我想触发类的一些私有行为,并且必须重新编译代码以使方法可见。我希望能够在REPL中直接调用私有Java方法,而无需更改代码 到目前为止,我得到的是: // Calls private Java methods // We currently define an overload for every n-argument method // there's probably a way to do this in one method

我经常使用Scala REPL进行快速Java迭代和测试,但有时我想触发类的一些私有行为,并且必须重新编译代码以使方法可见。我希望能够在REPL中直接调用私有Java方法,而无需更改代码

到目前为止,我得到的是:

// Calls private Java methods
// We currently define an overload for every n-argument method
// there's probably a way to do this in one method?
def callPrivate(obj: AnyRef, methodName: String) = {
  val method = obj.getClass().getDeclaredMethod(methodName)
  val returnType = method.getReturnType
  method.setAccessible(true)
  println("Call .asInstanceOf[%s] to cast" format method.getReturnType.getName)
  method.getReturnType.cast(method.invoke(obj))
}

def callPrivate(obj: AnyRef, methodName: String, arg: AnyRef) = {
  val method = obj.getClass().getDeclaredMethod(methodName, arg.getClass())
  method.setAccessible(true)
  method.invoke(obj, arg)
}
可以像这样使用:

scala> callPrivate(myObj, "privateMethod", arg).asInstanceOf[ReturnedClass]
但这需要为每个n参数方法类型定义一个几乎重复的方法(并且需要外部强制转换,但我怀疑这是不可避免的)。有没有办法重构它,使一个函数可以处理任意数量的参数

注意:我使用的是Scala2.9.1,所以我正在寻找使用Java反射的解决方案。欢迎使用Scala反射来回答,但不要直接解决我的问题。

免责声明:自从上次我用Scala编程以来,已经有一段时间了,我周围没有任何Scala环境来测试我向您展示的内容。所以这里那里可能有一些小的语法错误,请耐心听我说。希望剩下的有用

理论上,您可以为我们的
callPrivate
方法提供一个额外的变量参数,用于指定方法参数:

def callPrivate(obj: AnyRef, methodName: String, parameters:AnyRef*) = {
  val parameterTypes = parameters.map(_.getClass())
  val method = obj.getClass.getDeclaredMethod(methodName, parameterTypes:_*)
  method.setAccessible(true)
  method.invoke(obj, parameters:_*)
}
然而,有一个缺陷。如果您在某个地方有一个签名如下的方法,这将不起作用:

public X someMethod(A parameter);
A
由类
B
继承(或实现)。如果您试图以这种方式调用Scala方法,
callPrivate(someObject,“someMethod”,new B())
它将不起作用,因为
getDeclaredMethod
查找将搜索
someMethod(B)
而不是
someMethod(A)
——即使
new B()
也是
A
类型

所以这是一个幼稚的实现。您可以潜在地获取所有方法参数的所有有效类型,并使用它们的所有组合执行
getDeclaredMethod
查找,然而,在这个方向上还有一个警告:您可能会遇到重载方法,这些方法接受同一组参数的不同组合,您将不知道要调用哪一个(即,您可能有
someMethod(A,B)
someMethod(B,A)
,您将无法知道应该调用哪一个)

避免这种情况的一种方法是强制调用方提供元组而不是原始实例,每个元组都有要使用的参数值和参数类型。因此,由调用方指定要调用的方法

def callPrivateTyped(obj: AnyRef, methodName: String, parameters:(AnyRef,Class[_])*) = {
  val parameterValues = parameters.map(_._1)
  val parameterTypes = parameters.map(_._2)
  val method = obj.getClass.getDeclaredMethod(methodName, parameterTypes:_*)
  method.setAccessible(true)
  println("Call .asInstanceOf[%s] to cast" format method.getReturnType.getName)
  method.invoke(obj, parameterValues:_*)
}

// for convenience
def callPrivate(obj: AnyRef, methodName: String, parameters:AnyRef*) = {
  callPrivateTyped(obj, methodName, parameters.map(c => (c, c.getClass)):_*)
}
这应该能奏效


另外,还有一件事:请记住,使用
getDeclaredMethod
的方式只会返回在
obj.getClass()
中实现的方法(具有任何作用域),这意味着它不会返回任何继承的方法。我不知道这是否是出于设计,如果不是,您需要在您的
obj

的超类上添加递归查找。非常感谢,这正是我需要的指针。我无法使强制转换工作(调用
method.getReturnType.cast()
只是返回
Any
,而不是
java.lang.Object
),但是varargs行为现在可以工作了。我冒昧地用我当前编译的实现更新了您的答案;如果你愿意,我可以把它作为一个单独的答案贴出来。还有改进的余地。我想,如果泛型放置得当,您将受益于类型推断,并避免对客户机进行强制转换。可能您需要像
callprivateTypted[T](…):T那样声明您的方法,并且在返回之前,您可以执行
method.invoke(obj,parameterValues:*).asInstanceOf(T)
。这样做,您可以将调用者更改为只调用
var-result:SomeType=callPrivateTyped(..)
(或者
var-result=callPrivateTyped[SomeType](..)
)-我不会将此添加到我的答案中,因为在没有测试的情况下编写泛型是一件棘手的事情。