我经常使用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参数方法类型定义一个近似重复的方法(并且需要外部强制转换,但我怀疑这是不可避免的)。有没有办法重构这个,以便一个函数可以处理任意数量的参数?
注意:我使用的是Scala 2.9.1,因此我正在寻找使用Java Reflection的解决方案。欢迎使用Scala Reflection的答案,但不要直接解决我的问题。
答案 0 :(得分:4)
免责声明:自上次我在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
继承(或实现)。如果您尝试以这种方式callPrivate(someObject, "someMethod", new B())
调用Scala方法,它将无法正常工作,因为getDeclaredMethod
查找将搜索someMethod(B)
而不是someMethod(A)
- 即使{{1} }}类型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()
的超类上添加递归查找。