Scala Reflection : why getMethods can return theva

2019-08-09 08:20发布

问题:

In Scala, I have an Abstract Class:

abstract class AbstractSQLParser {

  def apply(input: String): Int = {

    println(reserveWords)
    return 1
  }

  protected case class Keyword(str: String)
  // use Reflection here
  protected lazy val reserveWords: Int =
      this.getClass
          .getMethods
          .filter(_.getReturnType == classOf[Keyword])
          .length
}

and another extends it :

class SqlParserDemo extends AbstractSQLParser{

  def apply(input: String, onError: Boolean) : Int = {

    apply(input)
  }

  protected val CREATE = Keyword("CREATE")
  protected val TEMPORARY = Keyword("TEMPORARY")
  protected val TABLE = Keyword("TABLE")
  protected val IF = Keyword("IF")
  protected val NOT = Keyword("NOT")
  protected val EXISTS = Keyword("EXISTS")

  def func1 = Keyword("HI")
}

but when I call

val sqlParser = new SqlParserDemo
print(sqlParser.apply("hello", false))

the output is :

7

1

What confuses me is : why getMethods can return the val members ?

You see, in the sub-class, I have six val , and one function.

In Oracle doc,

public Method[] getMethods() throws SecurityException Returns an array containing Method objects reflecting all the public member methods of the class or interface represented by this Class object, including those declared by the class or interface and those inherited from superclasses and superinterfaces

回答1:

Java doesn't know about vals. vals are a Scala construct, not a Java construct. Ergo, Java reflection cannot return anything about a val, because there is no such thing as a val in Java. The closest thing to a Scala val in Java is a getter method, which is exactly how Scala vals are represented on the Java side, and which is exactly what you get when you use Java reflection instead of Scala reflection.

If you use Scala reflection, you get the following:

import scala.reflect.runtime.{universe => ru}

def getTypeTag[T: ru.TypeTag](obj: T) = ru.typeTag[T]

val theType = getTypeTag(sqlParser).tpe
theType.declarations
// => reflect.runtime.universe.MemberScope = 
//      SynchronizedOps(
//        constructor SqlParserDemo, 
//        value CREATE, 
//        value CREATE, 
//        value TEMPORARY, 
//        value TEMPORARY, 
//        value TABLE, 
//        value TABLE, 
//        value IF, 
//        value IF, 
//        value NOT, 
//        value NOT, 
//        value EXISTS, 
//        value EXISTS, 
//        method func1)


回答2:

In addition to @Jorg W Mittag answer. You could compile your Scala code and then analyze resulting class with Java decompiler. For example. Here is the Scala class with sole val:

class Test {
    val test = 5;
}

Compile it, when decompile and you get:

public class Test
{
   private final int test = 5;
   public int test() { return this.test; }
}

So what Java reflection finds is a synthetic method generated by Scala compiler, to add such thing as val into JVM.
Or, if you prefer to check bytecode:

.../Workspace/Scala/TestVal>javap -c Test
Compiled from "Test.scala"
public class Test {
  public int test();
    Code:
       0: aload_0
       1: getfield      #13                 // Field test:I
       4: ireturn

  public Test();
    Code:
       0: aload_0
       1: invokespecial #19// Method java/lang/Object."<init>":()V
       4: aload_0
       5: iconst_5
       6: putfield      #13                 // Field test:I
       9: return
}

See, public int test(); method immediately after class name.