Oleg
Oleg

Reputation: 941

Get private field in Scala trait using Java reflection

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?

Upvotes: 3

Views: 3770

Answers (2)

developer_hatch
developer_hatch

Reputation: 16224

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.

Upvotes: 4

Seth Tisue
Seth Tisue

Reputation: 30453

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").

Upvotes: 6

Related Questions