2014-05-23 116 views
1

我經常使用Scala REPL來快速進行Java迭代和測試,但有時我想觸發某個類的私有行爲,並且必須重新編譯代碼才能使該方法可見。我希望能夠直接在REPL中調用私有Java方法,而無需更改代碼。在Scala中調用私有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] 

但這需要定義每個正參數方法類型近乎重複的方法(和要求外部演員,但我懷疑這是不可避免的)。有什麼辦法可以重構這個,以便一個函數可以處理任意數量的參數?

注:我使用的是Scala 2.9.1,所以我正在尋找使用Java Reflection的解決方案。歡迎使用Scala反射的答案,但不要直接解決我的問題。

回答

3

免責聲明:自從上次我在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); 

AB類繼承(或執行)。如果您嘗試以這種方式調用您的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的超類上添加遞歸查找。

+0

非常感謝,這正是我需要的指針。我無法投射工作(調用'method.getReturnType.cast()'只是簡單地返回'Any'而不是'java.lang.Object'),但可變參數行爲現在可以工作。我冒昧地更新您的答案,並編寫當前的實施方案;如果你願意,我可以把它作爲一個單獨的答案發布。 – dimo414

+0

還有改進的餘地。我猜想,通過泛型,您可以從類型推斷中受益,並避免在客戶端進行投射。可能你需要聲明你的方法,比如'callPrivateTyped [T](...):T',在返回之前你可以做'method.invoke(obj,parameterValues:_ *)。asInstanceOf(T)'。這樣做,你可以改變你的調用者來調用'var result:SomeType = callPrivateTyped(....)'(或者'var result = callPrivateTyped [SomeType](....)') - 我不會把它添加到我的答案是因爲仿製藥在沒有測試的情況下編寫是一件棘手的事情。 – Claudio