Java/Scala получить ссылку на поле в виде типа

Java утверждает, что он является объектно-ориентированным и типичным, и Scala тем более.

Внутренние поля класса представлены классом Field, который вы можете получить с помощью API Reflection.

Мой вопрос: предоставляют ли эти языки какой-либо способ получить ссылку на поле в стиле typeafe? (А если нет, то почему на земле нет? Похоже на вопиющий недостаток)

Было бы очень полезно при сопоставлении объекта с каким-либо внешним представлением, например с html-полями в шаблоне, или с именами столбцов в базе данных, чтобы сохранить имена ссылок автоматически в синхронизации.

В идеале я хотел бы сказать что-то вроде:

&(SomeClass.someField).name() 

чтобы получить имя объявления поля, похожее на то, как перечисление java позволяет вам сказать:

MyEnum.SOME_INSTANCE.name()

[update:] после прочтения отзывов о том, что эта функциональность каким-то образом нарушит намерение API Reflection, я согласен с тем, что Reflection предназначен для вещей, которые неизвестны во время компиляции, и что именно поэтому настолько абсурдно иметь используйте его, чтобы узнать, что известно во время компиляции, а именно поля самого класса, которые он компилирует!

Компилятор предоставляет это для перечислений, поэтому, если компилятор может получить доступ к ссылке enum Field, чтобы разрешить MyEnum.SOME_INSTANCE.name(), тогда нет никакой логической причины, почему она также не может предоставить эту же функциональность для обычных классов.

Есть ли технологическая причина, почему эта функция не может быть доступна для обычных классов? Я не понимаю, почему нет, и я не согласен с тем, что эта функциональность "усложнит" вещи... напротив, это значительно упростит нынешние громоздкие методы API Reflection. Зачем заставить разработчиков в Reflection обнаруживать то, что известно во время компиляции?

[update # 2] как для полезности этой функции, вы когда-нибудь пробовали использовать API критериев в JPA или Hibernate для динамического построения запроса? Видели ли вы абсурдные методы работы, которые люди придумали, чтобы избежать необходимости передавать небезопасное строковое представление поля для запроса?

[обновление # 3] Наконец, новый JVM-язык под названием Ceylon прислушался к вызову и делает это тривиальным для выполнения!

Ответ 1

Мой вопрос: предоставляют ли эти языки какой-либо способ получить это полевое задание по типу?

Типы времени компиляции? Не то, что я знаю, по крайней мере, на Java. Нормальная цель отражения в Java заключается в том, что код может иметь дело с типами, о которых он не знает заранее, - редко (по моему опыту) быть в положении, когда вы хотите иметь возможность ссылаться на поле в известный тип. Это происходит, но это не очень часто.

(И если нет, почему на земле нет? Похоже на вопиющий недостаток)

Каждая функция должна быть спроектирована, реализована, протестирована и должна соответствовать балансу, обеспечивающему большую ценность, чем добавленная сложность на языке.

Лично я могу думать о возможностях, которые я бы скорее видел в Java, чем это.

Ответ 2

Жаль, что Java по-прежнему пропускает эту функцию. Эта особенность не добавит дополнительной сложности, поскольку она будет мешать другим аспектам языка. Более того, быть функцией, которая редко использовалась, не является оправданием. На каждом языке полно возможностей, и большинство проектов используют небольшое подмножество из них.

Я действительно не понимаю, почему язык позволяет мне это сделать:

Поле поля = MyClass.class.getField( "myField" );//подробный синтаксис, оцениваемый во время выполнения, а не безопасный по типу, должен иметь дело с исключениями рефлексивной операции

Но это не позволяет мне (что-то вроде этого):

Поле поля = MyClass:: myField;//компактный синтаксис, оцениваемый во время компиляции, без ввода типов, без исключений!

(оператор "::" - это просто предложение, заимствованное из java 8 или С++)

Ответ 3

В Scala вы можете использовать для этого макросы. См. Следующее:

Пример:

class Car(val carName: String);

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

Таким образом, это печатает имя поля "carName". Если вы переименуете поле "carName" в "cName", оно вместо этого напечатает "cName".

Macro:

В этом случае фактически дерево выражений "_.carName" передается обработчику макросов, а не исполняемому методу. В нашем макросе мы можем изучить это дерево выражений и узнать имя поля, к которому мы обращаемся.

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

Я немного вдохнул из http://blogs.clariusconsulting.net/kzu/linq-beyond-queries-strong-typed-reflection/. Сообщение относится к одной и той же проблеме, но относится к С#.


Упущения

Остерегайтесь того, что макрос должен вызываться точно так же, как указано выше. Например, следующее использование приведет к исключению компилятора (на самом деле это исключение Scala match в макросе).

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

Проблема заключается в том, что другое дерево выражений передается обработчику макросов. Для получения дополнительной информации об этой проблеме см. Scala Макросчитать значение для имени имени

Ответ 4

В Scala 2.11 мы можем использовать это:

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

Использование:

class User(val name: String)

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

И результат будет:

(класс mref.User, public java.lang.String mref.User.name())

Ответ 5

Почему на земле нет, потому что вы не знаете тип поля во время компиляции при использовании отражения. В этом весь смысл: предоставить вам доступ к информации о классе во время выполнения. Конечно, вы получите ошибку времени выполнения, если используете неправильный тип, но это не очень помогает.

К сожалению, столь же сложно, как и сохранить имена одинаковые, обычно даже сложнее сохранять типы одинаковыми, поэтому, вероятно, это не стоит для приложения, которое вы имеете в виду.

Прямо сейчас нет никакого способа сделать то, что вы хотите, с разумными усилиями в Scala или Java. Scala может добавить эту информацию в свои манифесты (или где-то еще), но в настоящее время это не так, и мне не ясно, что это стоит усилий.

Ответ 6

В Котлине это выглядит так:

SomeClass::someField.name