2014-05-23 2 views
1

나는 스칼라 REPL을 사용하여 빠른 자바 반복과 테스트를하기 위해 정기적으로 사용하지만 때로는 클래스의 일부 비공개 동작을 트리거하고 메서드를 볼 수 있도록 코드를 다시 컴파일해야한다. 코드를 변경할 필요없이 REPL에서 직접 개인 Java 메소드를 호출 할 수 있기를 원합니다.스칼라에서 private 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 인수 방식 유형에 대한 근처 중복 방법을 정의해야합니다 (그리고 필요 외부 출연자,하지만 피할 수없는 것 같아요). 한 함수가 여러 개의 인수를 처리 할 수 ​​있도록이 함수를 리펙토링하는 방법이 있습니까?

참고 : 저는 Java Reflection을 사용하는 솔루션을 찾고 있으므로 Scala 2.9.1을 사용하고 있습니다. Scala Reflection을 사용한 답변은 환영하지만 직접 문제를 해결하지는 못합니다.

답변

3

면책 조항 : 마지막으로 스칼라에서 프로그래밍 한 이래로 시간이 지났으며 내가 보여주고있는 것을 테스트하기 위해 스칼라 환경이 주위에 없습니다. 그래서 여기저기서 작은 문법 오류가있을 수 있습니다. 나머지는 당신이 메소드 매개 변수를 지정하는 별도의 변수를 인수로 우리 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()) 스칼라 메서드를 호출하려고 시도하면 조회가 대신 someMethod(B)을 검색합니다. 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() '를 호출하면'java.lang.Object' 대신에'Any'를 반환합니다.) 그러나 varargs의 동작은 이제 작동합니다. 내가 컴파일 한 나의 현재 구현으로 당신의 대답을 업데이트 할 자유를 얻었다. 당신이 원한다면 별도의 답변으로 게시 할 수 있습니다. – dimo414

+0

개선의 여지가 여전히 있습니다. 제네릭을 사용하면 형식 유추의 이점을 제대로 누리고 클라이언트에서의 캐스팅을 피할 수 있습니다. 아마도 당신은'callPrivateTyped [T] (...) : T'와 ​​같은 메서드를 선언 할 필요가 있고 반환하기 전에'method.invoke (obj, parameterValues ​​: _ *). asInstanceOf (T)'를 할 수 있습니다. 'var result : SomeType = callPrivateTyped (....)'(또는'var result = callPrivateTyped [SomeType] (....)')을 호출하도록 호출자를 변경할 수 있습니다. 제네릭은 테스트하지 않고 작성하는 까다로운 일이기 때문에 제 대답. – Claudio