使用Java反射在Scala trait中获取私有字段

3

我想在测试中使用私有 trait 字段。这是一个非常简单的例子:

//Works fine with class A, but not trait A
trait A {  
  private val foo = "Some string"
}

class Test extends A {
  val field = classOf[A].getDeclaredField("foo")
  field.setAccessible(true)
  val str = field.get(this).asInstanceOf[String]
}

我遇到了这个问题:

java.lang.NoSuchFieldException: foo at java.lang.Class.getDeclaredField

可以在这里查看实时示例。

如何使此代码片段可运行?


正如您从示例中所看到的,它也会抛出一个错误。 - Pavel
好的。但是如何避免它? - Oleg
也许这个答案可以帮助? - jrook
不使用反射... - cchantep
2个回答

6

A是一个特质(trait),Scala将其翻译为JVM接口。接口不能有字段,因此没有这样的字段。只有在实际混合到类中时,才会添加底层字段。

所以你需要做的第一件事是将classOf[A]更改为classOf[Test]

第二件事是将getDeclaredField("foo")更改为.getDeclaredField("A$$foo")


1
Seth你搞定了!你怎么知道要把值作为字符串询问是"A$$foo"? - developer_hatch
1
我检查了 javap -private Test 的输出。请注意,特质编码的细节可能因 Scala 版本而异。 - Seth Tisue
你太厉害了!我把这加入到我的原始答案里,但引用了你的话,希望你能得到正确的功劳。我花了很长时间才想明白这个问题,我还查看了输出结果,太聪明了 ;) - developer_hatch
1
只是一个小细节。在实际项目中,所有的类都属于某些包。因此,我必须写成.getDeclaredField("com.example.A$$foo") - Oleg

4

编辑 特别感谢 @Seth Tisue(请为他投票并接受答案)

class Test extends A {
  val field:Field = this.getClass.getDeclaredField("A$$foo")

  field.setAccessible(true)

  println(field.get(this).asInstanceOf[String])

}

"this.getClass"是获取超类属性的正确方式,使用"A$$foo"和它不同。在进行修正后,您的代码将能够正常工作!
trait A {
  private val foo = "Some string"
}


class Test extends A {
  val fields: Seq[Field] = this.getClass.getDeclaredFields.toList

  val field = fields.filter(x => {
    println(x.getName)
    x.getName.contains("foo")
  }).head

  field.setAccessible(true)

  println(field.get(this).asInstanceOf[String])

}

正如您所看到的,当您打印“foo”变量的名称时,它并不是真正的“foo”,而是其他东西:
A$A295$A$A295$A$$foo

在我的情况下,这就是为什么你(和我)会得到错误的原因。
java.lang.NoSuchFieldException: foo at java.lang.Class.getDeclaredField

我的想法是,暂时这样,希望有更好的想法。就是查看变量名"A$A295$A$A295$A$$foo"中是否包含"foo",这样你就可以确定这就是你要找的变量。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接