Java/Scala obtain a Field reference in a typesafe way

It is a pity that Java still misses this feature. This feature would not add additional complexity because it would interfere with other aspects of the language. Furthermore, being a feature that would be rarely used is not excuse. Every language is full of features and most projects make use of a small subset of them.

I really don't understand why the language allows me to do this:

Field field = MyClass.class.getField("myField"); // verbose syntax, evaluate at runtime, not type-safe, must deal with Reflective operation exceptions

But it doesn't let me do (something like) this:

Field field = MyClass::myField; // compact syntax, evaluated at compile-time, type-safe, no exceptions!

(the "::" operator is just a suggestion, borrowed from java 8 or c++)


In Scala you can you might use macros for it. See the following:

Example:

class Car(val carName: String);

object Main {
  def main(args: Array[String]): Unit = {
    println(FieldNameMacro.getFieldName[Car](_.carName))
  }
}

So this prints the field name "carName". If you rename the field "carName" to "cName" it would print "cName" instead.

Macro:

In this case actually the expression tree of "_.carName" is passed to the macro handler, rather a executable method. In our macro we can look into this expression tree and find out the name of the field that we are referring to.

import scala.reflect.macros.whitebox.Context
import scala.language.experimental.macros
import scala.reflect.runtime.universe._
import scala.reflect.ClassTag

object FieldNameMacro {
  def getFieldNameImpl[T](c: Context)(block: c.Expr[T => AnyRef]): c.Expr[String] = {
    import c.universe._
    // here we look inside the block-expression and 
    // ... bind the TermName "carName" to the value name
    val Expr(Function(_, Select(_, TermName(name: String)))) = block;
    // return the name as a literal expression
    c.Expr(Literal(Constant(name)));
    // Uncomment this to get an idea of what is "inside" the block-expression
    // c.Expr(Literal(Constant(showRaw(block))));
  }

  def getFieldName[T](block: (T) => AnyRef): String = macro getFieldNameImpl[T]
}

I took some inspiration from http://blogs.clariusconsulting.net/kzu/linq-beyond-queries-strong-typed-reflection/. The post is about the same issue but with respect to C#.


Shortcomings

Beware that the Macro has to be called exactly as above. As for instance the following usage will lead to a compiler exception (actually it is a Scala match exception within the macro).

object Main {
  def main(args: Array[String]): Unit = {
    val block = (car : Car) => car.carName;
    println(FieldNameMacro.getFieldName[Car](block))
  }
}

The problem is that a different expression tree is passed to the macro handler. For more details about this problem have a look at Scala Macro get value for term name


In Scala 2.11 we can use this:

object R_ {
  def apply[T](x: (T) => AnyRef): (Class[T], Method) = macro impl
  def impl(c: whitebox.Context)(x: c.Tree) = { import c.universe._
    val q"((${_: TermName}:${a: Type}) => ${_: TermName}.${p: TermName})" = x

    val typeDef = a.typeSymbol
    val propertyDef = p.toString

    q"(classOf[$typeDef], classOf[$typeDef].getMethod($propertyDef))"
  }
}

Usage:

class User(val name: String)

object Test extends App {
  println(R_((a: User) => a.name))
}

And result will be:

(class mref.User,public java.lang.String mref.User.name())


My question: do these languages provide any way to obtain that Field reference in a typesafe way?

Compile-time typesafe? Not that I'm aware of, at least in Java. The normal purpose of reflection in Java is for code to be able to deal with types it has no knowledge of before-hand - it's rare (in my experience) to be in a position where you want to be able to refer to a field in a known type. It does happen, but it's not very common.

(And if not, why on earth not? Seems like a glaring deficiency)

Every feature needs to be designed, implemented, tested, and has to meet the balance of providing more value than the added complexity in the language.

Personally I can think of features I'd much rather see in Java than this.