Reflection and Macros use many APIs because they are basically the same thing: Meta Programming. You can create and execute code, you must reflect types, etc. Of course, there are some differences: at compile time you cannot display instances of the runtime and at runtime you do not get access to the internal structure of methods, scope and other information that is deleted at compile time.
Both APIs are still experimental and are likely to change in some parts in the future, but they are very useful and well documented enough. Scala, being such a universal language, they are much more complex than the API in Java.
In this documentation you are very far:
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/ (bottom of page)
This getClass[AnnotatedClass].getAnnotations gives you only Java annotations to get Scala Annotations, you need to get a Scala type instead of a class.
Access to reflections is possible at runtime, as well as during compilation, however there are three types of annotations:
Simple annotations that are only in code: they can be accessed from macros in the compilation unit, where the macro is called where the macro accesses the AST
StaticAnnotations, which are divided by compilation modules: they can be accessed through the Scala reflection api
ClassfileNnotations: they represent annotations stored as java annotations. If you want to access them through the Java Reflection API, you have to define them in Java, though.
Here is an example:
@SerialVersionUID(1) class Blub
Now we can get the annotation as follows:
import scala.reflect.runtime.universe._ val a = typeOf[Blub].typeSymbol.annotations.head
What we actually get is not an instance of annotation. The runtime simply gives us what is written in byte code: the Scala code generating the annotation. You can print the AST you receive:
showRaw(a.tree)
Now this is already a rather complex structure, but we can decompose it using pattern matching:
val Apply(_, List(AssignOrNamedArg(_,Literal(Constant(value))))) = a.tree val uid = value.asInstanceOf[Long]
This is normal for very simple annotations (but we could write them in Java and rely on JVM instances for us). What if we really want to evaluate this code and instantiate an annotation class? (For @SerialVersionUID this will not help us, since the class does not actually give access to id ...) We can also do this:
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) }
Note that this will call the compiler, which takes a little time, especially if you are doing this for the first time. Sophisticated metaprogramming must be performed at compile time in Scala. Many things in Java are executed only at runtime, because you only have meta-programming of the runtime (well, there are processors with annotations, but they are more limited).