从版本2.11开始,scala反射功能(特别是wrt注释)的(当前)状态是什么?

时间:2016-02-07 08:43:08

标签: java scala reflection annotations

scala似乎是JVM世界的一个很好的补充。它让我想起了一个奇怪的C ++,C#和Swift混合体,嵌套在JVM世界中。

但是,由于缺少或过时的文档,许多scala的功能可能无法访问。

就其反射能力而言,这似乎尤其如此。

例如,我正在评估是否可以使用scala注释在运行时或编译时扩充scala类。我使用的是最新的scala版本2.11。作为一个激励性的例子,让我说我做了case class SimpleAnnotation() extends StaticAnnotation。我想在运行时使用该注释找到所有case class

这可能是注释中最典型和最简单的用例。

在C#和Java中,在运行时确定给定的类是否被注释是相对简单的。这是一个规范的用例,带有规范的答案。然而,在scala中,我不清楚我应该做些什么来实现这种行为,甚至是否可能。特别是,在scala注释和反射上扫描了一些以前的材料后,我想知道:

  • 这可能吗?
  • 这是否只能在运行时或complile时间使用?
  • 仅在scala版本2.10之前或之后才能实现这一点吗?
  • 这是否只能在scala类上使用Java注释?
  • 为什么getClass[AnnotatedClass].getAnnotations会返回这些看似乱码的信息?
  • 为什么scala中的宏和反射似乎有些混淆?

任何指导都表示赞赏......我确信我不是唯一一个感到困惑的人。

1 个答案:

答案 0 :(得分:7)

Reflection和Mac分享了很多API,因为它们基本上是一样的:元编程。您可以生成并执行代码,您必须反映类型等。当然存在一些差异:在编译时您无法反映运行时实例,并且在运行时您无法访问编译期间删除的方法,范围和其他信息的内部结构。

这两个API仍然是实验性的,将来可能会在某些部分发生变化,但它们非常有用,而且记录完备。 Scala是一种多功能的语言,它们比Java中的API要复杂得多。

本文档为您带来了很多:

http://www.scala-lang.org/api/2.11.7/scala-reflect/

http://www.scala-lang.org/api/2.11.7/scala-compiler/

http://docs.scala-lang.org/overviews/(页面底部)

getClass[AnnotatedClass].getAnnotations仅为您提供Java注释,以获取Scala注释,您必须获取Scala类型而不是仅获取类。

可以在运行时和编译期间访问反射,但是有三种注释:

  1. 仅在代码中的纯注释:可以从编译单元中的宏访问这些注释宏,这些宏可以访问AST

  2. 通过编译单元共享的StaticAnnotations:这些可以通过scala reflection api访问

  3. ClassfileAnnotations:这些表示存储为java注释的注释。如果要通过Java Reflection API访问它们,则必须使用Java定义它们。

  4. 以下是一个例子:

    @SerialVersionUID(1) class Blub
    

    现在,我们可以通过这种方式获得注释:

    import scala.reflect.runtime.universe._
    val a = typeOf[Blub].typeSymbol.annotations.head
    

    我们实际获得的不是注释的实例。运行时环境只是为我们提供了在字节代码中编写的内容:生成注释的scala代码。你可以打印出你得到的AST:

    showRaw(a.tree)
    

    现在,这已经是一个非常复杂的结构,但我们可以使用模式匹配来分解它:

    val Apply(_, List(AssignOrNamedArg(_,Literal(Constant(value))))) = a.tree
    val uid = value.asInstanceOf[Long]
    

    这对于非常简单的注释是可以的(但我们可以用Java编写它们并依赖于JVM为我们创建实例)。如果我们真的想要评估该代码并生成注释类的实例,该怎么办? (对于@SerialVersionUID,这对我们没有多大帮助,因为该类实际上没有提供对id的访问权限......)我们也可以这样做:

    case class MyAnnotation(name: String) extends annotation.ClassfileAnnotation
    
    @MyAnnotation(name = "asd")
    class MyClass
    
    object MyApp extends App {
      import reflect.runtime.universe._
      import scala.reflect.runtime.currentMirror
      import scala.tools.reflect.ToolBox
      val toolbox = currentMirror.mkToolBox()
      val annotation = typeOf[MyClass].typeSymbol.annotations.head
      val instance =  toolbox.eval(toolbox.untypecheck(annotation.tree))
            .asInstanceOf[MyAnnotation]
      println(instance.name)
    }
    

    注意,这会调用编译器,这需要一点时间,特别是如果你是第一次这样做的话。复杂的元编程应该在Scala的编译时完成。 Java中的许多东西只在运行时完成,因为你只能运行时元编程(嗯,有注释处理器,但它们更受限制)。