Cannot use getDeclaredFields() to retrieve fields

2019-04-26 03:43发布

问题:

I'm trying to use a Java library (JOhm) with Scala and noticed it fails when the lib tries to read the fields of my Scala classes with something like model.getClass().getDeclaredFields().

Then I decided to try to do the same with simple examples in the Scala interpreter:

scala> import java.lang.reflect.Field;
import java.lang.reflect.Field

scala> class myClass(attribute1: String, attribute2: String, attribute3: String)
defined class myClass

scala> val myInstance = new myClass("value1", "value2", "value3")
myInstance: myClass = myClass@7055c39a

scala> myInstance.getClass().getDeclaredFields()
res0: Array[java.lang.reflect.Field] = Array()

Indeed, we get no field at all.

Now, what if I try this:

scala> class myClass2(attribute1: String, attribute2: String, attribute3: String) { override def toString = this.attribute1 }
defined class myClass2

scala> val myInstance2 = new myClass2("value1", "value2", "value3")
myInstance2: myClass2 = value1

scala> myInstance2.getClass().getDeclaredFields()
res1: Array[java.lang.reflect.Field] = Array(private final java.lang.String myClass2.attribute1)

So if use one of the fields in one of the class' methods, it is found by getDeclaredFields(). What am I missing here?

回答1:

What you are missing is that constructor parameters are not automatically promoted to fields.

Rather, they are promoted only if they are used. You used attribute1 so it was turned into a field; you didn't use the others so they were not.

If you declare them as val or var, or the class is a case class, they will also be promoted to fields (since they will actually have accessor methods generated, and thus are used).



回答2:

If you mark the fields as val or var, then getDeclaredFields will find them, e.g,

class myClass(val attribute1: String)

The JavaDoc for getFields says that it returns "all the accessible public fields", so it makes sense that the fields are not listed unless they are made public explicitly (per default, constructor arguments are private vals). However, the JavaDoc for getDeclaredFields does not mention such a limitation, but the visibility of fields apparently has an effect here, too.


Edit in response to @Clément:

import java.lang.reflect.Field

class Foo(val a1: String, private val a2: String, a3: String, a4: String) {
  val f = 10
  def foo(s: String) = a4 + s
}

val foo = new Foo("v1", "v2", "v3", "v4")

foo.getClass().getDeclaredFields().foreach(println)
  /* {a1, a2, a4, f} */

foo.getClass().getFields().foreach(println)
  /* {} */


回答3:

My guess would be that this is because the Scala compiler does not generate fields for all constructor arguments. If you add a var or val to the class definition fields will be generated:

scala> class myClass3(val attribute1:String, attribute2:String, attribute3:String)
defined class myClass3

scala> val myInstance3 = new myClass3("value1", "value2", "value3")
myInstance: myClass3 = myClass3@fd9178

scala> myInstance3.getClass().getDecalaredFields()
res1: Array[java.lang.reflect.Field] = Array(private field java.lang.String myClass3.attribute1)

Notice that for the two last constructor parameters there is no field generated. That is because they are mere constructor parameters and do nothing. The reason it works when you override the toString function I think is actually just because the compiler generates a hidden field that is used when the argument is accessed in the toString method. I do not think this should be relied on. You are better off explicitly stating what constructor parameters are your fields.