在Scala中调用私有Java方法
我经常使用Scala REPL进行快速Java迭代和测试,但有时我想触发类的一些私有行为,并且必须重新编译代码以使方法可见。我希望能够在REPL中直接调用私有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
// 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](..)
)-我不会将此添加到我的答案中,因为在没有测试的情况下编写泛型是一件棘手的事情。