Scala 反射可以提取特征中使用的初始值吗?
我在玩弄反思,以便对一种特质进行深入分析。我希望得到的一个结果是为成员字段设置的初始值。例如,在trait中:Scala 反射可以提取特征中使用的初始值吗?,scala,reflection,scala-2.10,Scala,Reflection,Scala 2.10,我在玩弄反思,以便对一种特质进行深入分析。我希望得到的一个结果是为成员字段设置的初始值。例如,在trait中: trait A { val x: Int = 3 val y: String = "y" } 知道3和“y”会很好。由于以下输出(由scalac-Xprint生成),我在API中未找到任何与此任务相关的内容: 抽象特征扩展对象{ def com$hablaps$A$_setter_u$x(x$1:Int):单位; def com$hablaps$A$_setter_u$y=(
trait A {
val x: Int = 3
val y: String = "y"
}
知道3和“y”会很好。由于以下输出(由scalac-Xprint生成),我在API中未找到任何与此任务相关的内容:
抽象特征扩展对象{
def com$hablaps$A$_setter_u$x(x$1:Int):单位;
def com$hablaps$A$_setter_u$y=(x$1:String):单位;
def x():Int;
def y():字符串
};
$class扩展的抽象特性{
def/*A$class*/$init$($this:com.hablaps.A):单位={
$this.com$hablaps$A$\u setter\$x\=(3);
$this.com$hablaps$A$\u setter\u$y=(“y”);
()
}
}
我担心访问它们会非常困难,因为它们保存在$init$方法的主体中。有什么(简单的)方法可以通过反射获得这些值吗?我怀疑你能反思到这一点。这不是关于类型的信息,而是代码。如果你有这棵树的特质,你可以找到它,但是,否则,我怀疑它 但是,您可以使用类文件解析器来进一步研究这个问题。我假设这些将显示为类的常量,可以读取。我不确定你能不能把它们和变量联系起来,但是
我不太熟悉类文件解析器,但我认为一个名为“asm”的工具可以做到这一点。您必须反汇编字节码:
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$\u setter\u$x\u$eq
扩展为什么都不做——使成为一个$class。$init$
调用一个no op并使该值不可恢复
证明:
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
您可以使用Java的代理来创建trait的实例,该实例从对trait的…$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)
}
对象反射{
def traitInits(clazz:Class[]):Map[String,Object]={
var cl=clazz.getClassLoader
如果(cl==null){
cl=ClassLoader.getSystemClassLoader
}
var init:Option[java.lang.reflect.Method]=None
试一试{
对于(m)
}
if(init.isEmpty)返回映射()
var encodedCodedSetterNameap=Map[String,String]()
用于(m解码)
}
}
var result=Map[字符串,对象]()
导入java.lang.reflect.InvocationHandler
导入java.lang.reflect.Proxy
init.get.invoke(null,Proxy.newProxyInstance(cl,数组[Class[]](clazz)),
新调用处理程序{
def调用(代理:对象,
方法:java.lang.reflect.method,
args:Array[Object])={
encodedCodedSetterNameap.get(method.getName)匹配{
案例部分(decodedName)=>result+=(decodedName->args(0))
案例=>
}
无效的
}
}))
结果
}//>traitInits:(clazz:Class[])Map[字符串,对象]
特征A{
val x:Int=3
val y:String=“y”
}
traitInits(classOf[A])/>res0:Map[String,Object]=Map(x->3,y->y)
}
我忘了说我一直在关注这一点,这对于从trait中提取大量信息非常有用。我不熟悉Scala反射,但是如果您能够执行新的{}.x
,那么你应该没问题。是的,实例化并获取值,不管是否通过反射。希望没有任何副作用。是的,在这种情况下它肯定会工作,但我隐藏了一些细节:反射在一个函数中工作,该函数接收特性作为类型参数(analyze[t]
)而且trait不一定要带有初始值。因此,生成实例可能会变得非常复杂,我一直在寻找更干净的方法。无论如何,我到目前为止还没有考虑过这个简单的解决方案,如果其他任何方法都失败了,考虑它可能会很有趣。谢谢!是的,我得出了结论我认为拥有树会很好,但是有没有办法从TypeTag中获得它呢?无论如何,我会看看ASM。谢谢,Daniel。是的,C
缺少3,但它仍然可以使用重写7,它将成为C
的初始值。因此,在分析这个类的情况下,我不关心a
初始值都不是。相反,我想知道7。无论如何,这是非常有趣的东西。你是如何生成输出的?@JesúsLópez这是用javap
完成的。REPL上甚至有一个:javap
命令,但它需要JDK中的tools.jar
位于类路径上,并且它只适用于预编译的stuff、 不是为了你在REPL本身上定义的东西。@JesúsLópez-你说的“初始值”是什么意思?它是一个val。它不能更改。如果C
直接覆盖a
,这与B
的情况是一样的——该值很可能只存在于字节码中,并且不可访问,因为a$\u setter\u$x\u$eq
被覆盖。@DanielC.Sobral-:javap
将对您在REPL中定义的内容起作用;它只是一点点我对此很挑剔,因为它优先于给定名称的第一件事(不是范围中的当前名称),不能访问辅助类,并且还有一些其他的怪癖。@RexKerr,我从来没有让它工作过。它给了我找不到“myclass”的类字节。
。
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)
}