我正在玩反思以实现对特质的深入分析。我想得到的一件事是设置成员字段的初始值。例如,在特征中:
trait A {
val x: Int = 3
val y: String = "y"
}
知道3和“y”会很高兴。我没有在API中找到与此任务相关的任何内容,并且由于以下输出(由scalac -Xprint生成):
abstract trait A extends Object {
<accessor> def com$hablapps$A$_setter_$x_=(x$1: Int): Unit;
<accessor> def com$hablapps$A$_setter_$y_=(x$1: String): Unit;
<stable> <accessor> def x(): Int;
<stable> <accessor> def y(): String
};
abstract trait A$class extends {
def /*A$class*/$init$($this: com.hablapps.A): Unit = {
$this.com$hablapps$A$_setter_$x_=(3);
$this.com$hablapps$A$_setter_$y_=("y");
()
}
}
我担心访问它们会很困难,因为它们保存在$ init $方法的主体中。是否有任何(简单)方法可以通过反射获得这些值?
答案 0 :(得分:2)
你必须反汇编字节码:
trait A { val x: Int = 3 }
public abstract class A$class extends java.lang.Object{
public static void $init$(A);
Code:
0: aload_0
1: iconst_3
2: invokeinterface #12, 2; //InterfaceMethod A.A$_setter_$x_$eq:(I)V
7: return
参见第1行 - 值存在的唯一位置是init方法的字节码!
你不能以任何其他方式达到这个目的,因为如果你有
trait A { val x: Int = 3 }
trait B extends A { override val x = 7 }
class C extends B {}
您发现C
扩展A$_setter_$x_$eq
根本不执行任何操作 - 使A$class.$init$
调用为无操作并使值无法恢复。
证明:
public class C extends java.lang.Object implements B,scala.ScalaObject{
public void A$_setter_$x_$eq(int);
Code:
0: return
public void B$_setter_$x_$eq(int);
Code:
0: aload_0
1: iload_1
2: putfield #11; //Field x:I
5: return
答案 1 :(得分:1)
我怀疑你能否反思这一点。这不是关于类型的信息,而是代码。如果你有这个特性的树,你可以找到它,但是,否则,我怀疑它。
但是,您可以使用类文件解析器来进一步调查此问题。我假设这些将作为类的常量出现,可以读取。我不确定你是否可以将它们与变量相关联,但是......
我对类文件解析器不太熟悉,但我认为一个名为“asm”的工具可以做到这一点。
答案 2 :(得分:1)
您可以使用Java的代理创建特征实例,该特征从所有对特征的... $ setter $ ...方法的调用中收集值。这是一个黑客的例子:
object Reflection {
def traitInits(clazz : Class[_]) : Map[String, Object] = {
var cl = clazz.getClassLoader
if (cl == null) {
cl = ClassLoader.getSystemClassLoader
}
var init : Option[java.lang.reflect.Method] = None
try {
for (m <- cl.loadClass(clazz.getName + "$class").getMethods if init.isEmpty)
if (m.getName == "$init$")
init = Some(m)
} catch {
case e : Exception =>
}
if (init.isEmpty) return Map()
var encodedToDecodedSetterNameMap = Map[String, String]()
for (m <- clazz.getDeclaredMethods()) {
val setterPrefix = clazz.getName.replace('.', '$') + "$_setter_$"
val encoded = m.getName
if (encoded.startsWith(setterPrefix)) {
val decoded = encoded.substring(setterPrefix.length, encoded.length - 4)
encodedToDecodedSetterNameMap += (encoded -> decoded)
}
}
var result = Map[String, Object]()
import java.lang.reflect.InvocationHandler
import java.lang.reflect.Proxy
init.get.invoke(null, Proxy.newProxyInstance(cl, Array[Class[_]](clazz),
new InvocationHandler {
def invoke(proxy : Object,
method : java.lang.reflect.Method,
args : Array[Object]) = {
encodedToDecodedSetterNameMap.get(method.getName) match {
case Some(decodedName) => result += (decodedName -> args(0))
case _ =>
}
null
}
}))
result
} //> traitInits: (clazz: Class[_])Map[String,Object]
trait A {
val x : Int = 3
val y : String = "y"
}
traitInits(classOf[A]) //> res0: Map[String,Object] = Map(x -> 3, y -> y)
}