11

Java 声称是面向对象和类型安全的,Scala 更是如此。

内部类字段由名为 Field 的类表示,您可以通过反射 API 获取对其的引用。

我的问题:这些语言是否提供了以类型安全的方式获取该字段引用的任何方法?(如果不是,为什么不呢?似乎是一个明显的缺陷)

在将 Object 映射到某些外部表示时,例如映射到模板中的 html 字段或数据库中的列名,以使引用名称自动保持同步,这将非常有用。

理想情况下,我想说的是:

&(SomeClass.someField).name() 

获取字段声明的名称,类似于 java 枚举让你说的方式:

MyEnum.SOME_INSTANCE.name()

[更新:] 在阅读了有关此功能会以某种方式违反反射 API 意图的反馈后,我同意反射是为编译时未知的事物而设计的,这正是为什么必须使用它来实现学习编译时已知的东西,即它正在编译的类的字段!

编译器为枚举提供了此功能,因此如果编译器能够访问枚举字段的引用以允许 MyEnum.SOME_INSTANCE.name(),那么它不应该也能够为普通类提供相同的功能没有逻辑上的理由.

是否有任何技术原因导致普通课程无法提供此功能?我不明白为什么不这样做,我不同意这个功能会使事情“复杂化”……相反,它会大大简化目前繁琐的反射 API 技术。为什么要强迫开发人员使用反射来找出编译时已知的东西?

[更新#2] 至于这个功能的实用性,你有没有尝试过在 JPA 或 Hibernate 中使用 Criteria API 来动态构造查询?您是否看到人们想出一些荒谬的变通办法来避免必须传入要查询的字段的不安全字符串表示形式?

[更新 #3] 最后,一种名为 Ceylon 的新 JVM 语言已经注意到了这一呼吁,并让这件事变得轻而易举

4

6 回答 6

6

我的问题:这些语言是否提供了以类型安全的方式获取该字段引用的任何方法?

编译时类型安全?我不知道,至少在 Java 中是这样。Java 中反射的正常目的是让代码能够处理它事先不知道的类型 - 很少(根据我的经验)处于您希望能够引用其中的字段的位置已知类型。它确实会发生,但并不常见。

(如果没有,为什么不呢?似乎是一个明显的缺陷)

每个功能都需要设计、实现、测试,并且必须满足提供更多价值而不是增加语言复杂性的平衡。

就个人而言,我可以想到我更愿意在 Java 中看到的功能,而不是这个。

于 2012-03-24T21:36:03.090 回答
5

遗憾的是Java仍然错过了这个特性。此功能不会增加额外的复杂性,因为它会干扰语言的其他方面。此外,作为一个很少使用的功能也不是借口。每种语言都充满了特性,大多数项目都使用其中的一小部分。

我真的不明白为什么语言允许我这样做:

字段字段 = MyClass.class.getField("myField"); // 语法冗长,在运行时计算,不是类型安全的,必须处理反射操作异常

但它不允许我这样做(类似):

字段字段 = MyClass::myField; // 紧凑的语法,在编译时评估,类型安全,没有异常!

(“::”运算符只是一个建议,借鉴自 java 8 或 c++)

于 2016-11-10T09:58:44.873 回答
3

在 Scala 中,您可以使用宏。请参阅以下内容:

例子:

class Car(val carName: String);

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

所以这会打印字段名称“carName”。如果您将字段“carName”重命名为“cName”,它将改为打印“cName”。

宏:

在这种情况下,实际上“_.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/获得了一些灵感。这篇文章是关于相同的问题,但与 C# 有关。


缺点

请注意,必须完全按照上述方式调用宏。例如下面的用法会导致编译器异常(实际上是宏中的 Scala 匹配异常)。

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

问题是不同的表达式树被传递给宏处理程序。有关此问题的更多详细信息,请查看Scala Macro get value for term name

于 2014-07-14T12:13:09.923 回答
3

在 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())

于 2014-12-23T20:16:59.480 回答
1

为什么不这样做是因为您在使用反射时在编译时不知道该字段的类型。这是反思的重点:让您在运行时访问类信息。当然,如果你使用了错误的类型,你会得到一个运行时错误,但这并没有多大帮助。

不幸的是,尽管保持名称相同很棘手,但保持类型相同通常更棘手,因此对于您心目中的应用程序来说可能不值得。

现在,无论是在 Scala 还是 Java 中,都无法通过合理的努力来做你想做的事。Scala可以将此信息添加到它的清单(或其他地方),但目前还没有,而且我不清楚这是否值得付出努力。

于 2012-03-24T21:52:05.463 回答
0

在 Kotlin 中,它看起来像这样:

SomeClass::someField.name

它非常接近您的理想:

&(SomeClass.someField).name()

于 2019-05-15T18:19:08.360 回答