3

I am trying to use private trait field in testing. Very simple example:

//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]
}

I got:

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

live example here

How to get this snippet executable?

Seth Tisue
  • 29,985
  • 11
  • 82
  • 149
Oleg
  • 899
  • 1
  • 8
  • 22

2 Answers2

6

A is a trait, which Scala translates to a JVM interface. Interfaces can't have fields, so there is no such field. The underlying field only gets added once the interface is actually mixed into a class.

So the first thing you would need to do get this is running is change classOf[A] to classOf[Test].

The second thing is to change getDeclaredField("foo") to .getDeclaredField("A$$foo").

Seth Tisue
  • 29,985
  • 11
  • 82
  • 149
  • 1
    Seth you got it! how did you know that the value to ask as string was "A$$foo"? – developer_hatch Oct 31 '17 at 18:23
  • 1
    I inspected the output of `javap -private Test`. Note that the details of how traits are encoded may vary from Scala version to Scala version. – Seth Tisue Oct 31 '17 at 18:24
  • you rules! really, I added it to my original answer, but quoting you, I hope you get the correct credit for that, I was a long time trying to figure out that, I also looked out for the output, genius ;) – developer_hatch Oct 31 '17 at 18:28
  • 1
    Just a small point. In real-world projects all classes belongs to some packages. So i have to write `.getDeclaredField("com.example.A$$foo")` – Oleg Nov 01 '17 at 07:06
4

Edit special thanks to @Seth Tisue (up votes and accept to him please)

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

  field.setAccessible(true)

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

}

"A$$foo" is the correct way to get the super type attribute, this and use this.getClass. I didn't know it before, but with that correction, your code will work just great!

First Idea:

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])

}

As you can see, when you print the name of the "foo" variable, is not really "foo", it's something else:

A$A295$A$A295$A$$foo

in my case, and that's why you (and I) got the error

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

So, my idea for now, I hope someone came with a better one, is look if "foo" is inside the variable name "A$A295$A$A295$A$$foo" so you can tell thats the variable you where looking for.

developer_hatch
  • 15,898
  • 3
  • 42
  • 75