Idiomatic Kotlin: Annotations and Reflection

Tompee Balauag
Familiar Android
Published in
5 min readMay 9, 2018
“A small wire scultpure in a person's hands” by Ander Burdain on Unsplash

Note: This article is now a part of the Idiomatic Kotlin series. It was originally a 2-part series. The complete list is at the bottom of the article.

Annotation is a way of representing metadata. It is the preferred way of declaring source code metadata as it standardize format and function, and is closely coupled. Kotlin supports Java-like annotations and in this tutorial, we will discuss custom annotations and how they can be used through reflection.

Defining custom annotation

Defining an annotation can be as simple as this.

You can now use it to annotate your class, fields and methods. However, there are other properties that can be used to make your annotation class bounded and more powerful. Let’s take a look at them one by one.

@Target

This property allows you to set the constraints on which element an annotation can be used. Some of the well known targets are listed below. Full list can be found here.

  • CLASS — classes, interfaces, objects and annotation classes
  • FUNCTION— methods not including constructors
  • FIELD — field variables including the backing field
  • TYPE — Any expression

Multiple targets can be specified as well. As previously mentioned, @Target constrains the element in which the annotation can be attached. For example, annotating a method using the above code will result in a compile error.

@Retention

Retention specifies the scope of the annotation. There are three types of retention in Kotlin.

  • SOURCE— Annotation is only valid in compile time and is removed in binary output. This is similar to Java’s SOURCE retention.
  • BINARY— Annotation persists in binary output but cannot be accessed via reflection.
  • RUNTIME — Annotation persists in binary output and can be used via reflection. This is the default retention policy.

For the purpose of this tutorial, we will be using RUNTIME as we will try to demonstrate annotation and its use in reflection. However, there are other uses for annotation such as code generation but that is another topic in the next tutorial.

@Repeatable

Repeatable allows you to use the same annotation in a single type. Pretty straightforward. In Java, this is introduced in version 8. So if you are planning to use repeatable annotations, you must target at least a JVM 1.8 version.

@MustBeDocumented

This property allows the annotation to be included in the generated documentation. This is similar to Java’s @Documented.

Annotation Model

Annotation classes can have a constructor and members. However, members are constrained to the following types:

  • types that correspond to Java primitive types (Int, Long etc.);
  • strings;
  • classes (Foo::class);
  • enums;
  • other annotations;
  • arrays of the types listed above.

We can now define our own annotation. We will use it to attach information to our classes.

Reflection

Reflection is a technique that allows you to examine and modify your application structure at runtime. This is a very powerful tool, and very dangerous at the same time. We will learn why in a little bit.

The basic approach to reflection in Kotlin is getting an instance of the Kotlin class or KClass. This can be achieved using the class literal syntax (can be used on the object as well).

This KClass object exposes the static properties of your class. For example, the following properties are available.

  • isAbstract — True if the class is abstract
  • isCompanion — True if the class is a companion object
  • isData — True if the class is a data class
  • isFinal — True if the class is a final class
  • isInner — True if the class is an inner class
  • isOpen — True if the class is an open class
  • isSealed — True if the class is a sealed class

It can also enumerate the methods (members) and constructors (constructors) of your class.

Also, there is an separate library that contains extension functions for KClass called kotlin-reflect.Add this to your list of dependencies and lets investigate some of the functions.

implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"

An interesting method called createInstance allows you to create an instance of your class. Be careful when using this method as it will throw a runtime exception when used on a class without a no-arg constructor.

The code above will print the hello string if the class has no-arg constructor.

A property called primaryConstructor is also available. It if exists, it allows you to create an instance of the class by using it and providing arguments if necessary.

The above code will output the hello string if the primary constructor has a single string parameter. You can also iterate through the list of constructor if you want use the one you are comfortable with.

By now you are now aware of the power of reflection. It allows you to inspect and operate on a class at runtime. Reflection is used in many functions, libraries and frameworks such as dependency injection, serialization and unit testing. However, use it sparingly. As we have pointed out earlier, it can cause A LOT of runtime exception. Two things:

Annotations and Reflection

Now let us try to achieve what we came for. KClass also exposes the annotations that are attached within the class by implementing the KAnnotatedElement interface. Let us define a custom class and annotate it with our predefined annotation

There is a convenient method available in the reflection library that lets you find a particular annotation. Let’s look at an example.

findAnnotation looks for the specified annotation in you KClass instance and returns either a null if not found, or an instance. This instance can now be used to query the annotation class properties. Running the above code will output

D/Annotation Test: SourceData
D/Annotation Test: Benedict Cumberbatch
D/Annotation Test: 1
D/Annotation Test: 2018-05-09

Notice that this whole idea of attaching metadata to a source file is only useful in a static context. Instance level is not affected.

That’s it for the annotations and reflection starter pack. Hopefully, on the next tutorial, I can discuss about a more powerful use of annotation which is called code generation.

Check out the other articles in the idiomatic kotlin series. The sample source code for each article can be found here in Github.

  1. Extension Functions
  2. Sealed Classes
  3. Infix Functions
  4. Class Delegation
  5. Local functions
  6. Object and Singleton
  7. Sequences
  8. Lambdas and SAM constructors
  9. Lambdas with Receiver and DSL
  10. Elvis operator
  11. Property Delegates and Lazy
  12. Higher-order functions and Function Types
  13. Inline functions
  14. Lambdas and Control Flows
  15. Reified Parameters
  16. Noinline and Crossinline
  17. Variance
  18. Annotations and Reflection
  19. Annotation Processor and Code Generation

--

--