2012-03-24 51 views
10

Java声称是面向对象的,类型安全的,而Scala更是如此。Java/Scala以类型安全的方式获取字段引用

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

我的问题:这些语言是否提供任何方式来获得类型安全方式中的字段引用? (如果不是,为什么不是这样?看起来像是一个明显的缺陷)

当将一个Object映射到某个外部表示(例如模板中的html字段或数据库中的列名)时,这将非常有用,以保持参考名称自动同步。

理想我想这样说:

&(SomeClass.someField).name() 

获取字段声明,类似的名称了Java枚举让你说:

MyEnum.SOME_INSTANCE.name() 

[更新:]在阅读反馈意见之后,这个功能会以某种方式违反Reflection API的意图,我同意Reflection是针对在编译时不为人知的事情而设计的,这就是为什么使用它来学习那些事情是如此荒谬的原因:在编译时已知,即它正在编译的类的字段!

编译器为枚举提供了这个功能,所以如果编译器能够访问枚举Field的引用以允许MyEnum.SOME_INSTANCE.name(),那么为什么它不应该也能够提供相同的逻辑原因功能到普通的类。

是否有任何技术原因,为什么这个功能不能用于普通类?我不明白为什么不这样做,我不同意这种功能会使事情“复杂化”......相反,它将极大地简化目前繁琐的Reflection API技术。为什么要迫使开发人员加入Reflection来找出编译时已知的东西?

关于此功能的实用程序[更新#2],您是否尝试过使用JPA或Hibernate中的Criteria API来动态构造查询?你有没有看到人们想出的荒谬的解决方法,试图避免不必要传递字段的不安全字符串表示来查询?

[update#3]最后,一个名为Ceylon的新JVM语言已经注意到了这个呼叫,并使这trivial做!

+0

我真的不明白你的意思。如果'&(SomeClass.someField).name()'可以静态检测,为什么不直接写'SomeClass.someField.name()'? SomeClass.someField是一个字符串吗?如果是,编译器应该如何识别它的值(它可以在运行时填充)? – sschaef 2012-03-24 23:36:31

+0

重点不在于获取字符串的值,而是为了获取已分配给该字符串字段本身的名称。例如,如果该字段被定义为:public String title =“president”;那么我想调用&(SomeClass.title).name()并取回“标题” – Magnus 2012-03-25 02:13:19

+0

如果你知道名称为什么不直接访问它?您提到了“数据库中的列名”,但是当您更改该字段时,还必须更改对其的所有引用。这可以用现代的IDE来完成,而不需要使用你想要的功能。 – sschaef 2012-03-25 11:23:55

回答

6

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

编译时类型安全吗?不是我所知道的,至少在Java中。 Java中反射的正常目的是让代码能够处理它以前不知道的类型 - 根据我的经验,很少有人希望想要能够引用已知类型的字段。它确实发生,但它不是很常见。

(如果不是,为什么地球上不是?看起来像一个明显的缺陷)

每个功能都需要设计,实施,测试,并且必须达到提供比语言中增加的复杂性更多价值的平衡。

就我个人而言,我可以想到我宁愿在Java中看到的功能。

+2

我会认为这是罕见的*唯一的原因*这是因为人们没有*看到*它。我认为如果这个特性变得可用,那么在编译时就会出现一整类编程模式,目前仅在运行时才解决这个问题。 – Magnus 2012-03-25 15:25:02

+2

@Magnus:我会把“目前在运行时被繁琐地解决”称为“希望能够引用已知类型的字段”。我不是说它*不会有用 - 而且我知道C#团队已经考虑过类似的东西(http://blogs.msdn.com/b/ericlippert/archive/2009/05/21/in-foof -we-trust-a-dialogue.aspx),但我仍然认为这不会是你所暗示的世界变化的启示。 – 2012-03-25 15:30:57

+2

感谢您的链接,非常有趣的讨论!然而,我的建议是针对领域而不是方法,避免了所描述的“超负荷歧义”缺陷,而范围问题似乎是一个非问题:只要遵循正常的领域范围规则一如既往。尽管如此,启发性的讨论。 – Magnus 2012-03-26 21:10:20

1

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

不幸的是,尽管名称保持不变,但通常情况下更难以保持类型相同,所以它可能不值得您考虑的应用程序。

现在没有办法在Scala或Java中以合理的努力去做你想做的事情。斯卡拉可能将这些信息添加到它的清单(或其他地方),但它目前不是,我不清楚这是值得的努力。

+3

但是我们知道编译时的字段类型,它就在类定义中。如果反射使我通过字符串名称获取字段引用,为什么不让我直接从类定义中引用呢? – Magnus 2012-03-24 22:37:03

+0

你确实知道类型,但不通过reflection_。编译器可以明智地提供一个nameOf(myClass)。x)方法(无论是在Scala还是Java中),但它可能不会明智地创建一个getField(myClass,“x”)方法,该方法明智地输入,而没有明显不同的语言特性(即“x”和“date”这两个字符串都只是字符串,但是根据字符串的_content_,你会希望编译器推断两种不同的类型......如果字符串只是's',会发生什么?)。无论如何,在编译时反映类型:不,并不容易。将字段名称静态转换为字符串:可能。 – 2012-03-24 22:55:54

+4

这正是我的观点 - 语言设计者提供这种功能会很容易,但他们根本就没有。换句话说,这是一种遗漏(或者我会说“缺陷”)。 – Magnus 2012-03-24 23:10:40

2

在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

2

斯卡拉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,公共java.lang.String中mref.User.name() )

3

Java遗憾地遗漏了这个功能。此功能不会增加额外的复杂性,因为它会干扰该语言的其他方面。此外,作为一个很少使用的功能并不是理由。每种语言都有很多功能,大多数项目都使用它们的一小部分。

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

场场= MyClass.class.getField(“MyField的”); //详细的语法,评估在运行时,不是类型安全,必须处理反射操作异常

不过,这并不让我做(像)这样的:

场场= MyClass的:: MyField的; //紧凑的语法,在编译时评估,类型安全,没有例外!

(“::”运算符只是一个建议,从java 8或C++借用)