The Programmers Guide To Kotlin - Annotation & Reflection |
Written by Mike James |
Monday, 08 January 2018 |
Kotlin supports Java-like annotations but they don't really make much sense without the help of reflection. So let's take a look at both of them and see how they work together.
AnnotationsAnnotations are a strange idea when you first meet them. At their simplest you can regard them as instructions to the compiler. For example. you can change the name of the default class used to host functions that are not explicitly declared as methods using an annotation:
This is an instruction to the compiler to change the name and you cannot hope to implement custom annotations with a similar power unless you are prepared to modify the compiler. There are a few standard annotations and you can create your own custom annotations, although as indicated these don't integrate with the compiler in quite the same way. Custom annotations are generally used to pass information either to another programmer or to the code itself. For example the @Deprecated annotation marks a function as being one to avoid using because it is going to be removed in the future:
If you try to use the deprecated function then the compiler will show that there is a problem by showing it in struck out and display a suitable message: In this case you can see that the annotation is of use to any programmer building a library for others to use. It communicates information about how the function should be used to other programmers. Some annotations also indicate conditions to code that makes use of it, but to follow how this works we need to know about reflection and so this topic is deferred until later in the chapter. Annotations can be applied to classes, functions, properties and so on. There is a particular problem with using annotations in Kotlin because of the need to remain compatible with Java annotations. The problem is that Kotlin doesn't always expose all of the equivalent Java elements that an annotation may be applied to, and you may well want to annotate these “hidden” elements. To get around this problem Kotlin has some rules for writing annotations: To apply an annotation to a primary constructor you have to use the constructor keyword in the declaration:
You can annotate a lambda by writing the annotation in front of the opening curly bracket:
To attach an Annotation to a Java entity you have to specify the entity before writing the annotation. The supported entities are:
So for example to annotate the setter of a Kotlin property you would use:
You can also set multiple annotations on an entity by following the entity name by a list of annotations in square brackets. Annotations are useful when you want to associate data, usually referred to as meta data, to your code. To do this you have to create custom annotation classes and to make use of them you have to resort to reflection. So first we need to look at reflection and then return to custom annotations. ReflectionReflection is an advanced technique that gives you access to things that you don't normally have access to. You can use it to find out details of the classes and their methods and properties – things that you might suppose would not be accessible at runtime. As you might expect, Kotlin's reflection features are somewhat different to Java, but as always there are ways of working with Java reflection. The most basic form of reflection is to get an object, an instance of KClass, that corresponds to a Kotlin class. To retrieve the class as a KClass object all you have to do is:
What you do next is to access any of the class details you care to, using the methods and properties of the classobj. For example suppose MyClass is:
Then you can discover what member functions the class has using:
This lists the methods by name. Methods and functions in general are represented by KCallable objects. You can do some things you might not expect to be able to do in a statically typed language like Kotlin (or Java). For example, once you have the KClass object you can create an instance:
and you can call methods on that instance:
You can even call a method that you have obtained by reflection. The only thing you need to remember is that you have to supply the parameters correctly and the first parameter is this i.e. the call context or receiver. For example:
The this that you pass has to be the correct type of object. You can also get a KClass object from an instance of the class:
Properties can also be manipulated and in this case the objects you need to look up are the KProperty and KMutableProperty classes. We have already seen the use of the :: operator to get a reference to a function. It can also be used to get a reference to a bound method. For example:
stores a reference to myMethod bound to myObject. Now when you call myFunction() it calls myMethod with this set to myObject. You can also gain access to the Java reflection methods using the Kotlin KClass java property. When you first meet reflection it seems to give you great power, but with great power come great bugs. Use reflection sparingly if at all. Most of the difficulty is finding the method or property of the reflection classes that provides what you need. It may be obvious but it is worth saying that you cannot use reflection to change the definition of a class. You cannot add methods or properties at runtime. You can only inquire about and make use of members that were added at compile time. Custom AnnotationsNow that we have covered some aspects of reflection we can deal more easily with custom annotations. It is very easy to create a custom annotation. All you need to is create a class with the modifier annotation:
Now you have an annotation ready to use:
All you can really do with such a simple annotation class is to check to see if some entity has the annotation at runtime using reflection. For example the KClass object has an annotations property that returns a list of annotations:
This just prints MyAnnotation. The only complication is that an annotation can be attached to a class or any member of a class/object. You have to use reflection to retrieve an object that represents that entity, and then use annotations to retrieve its annotations. If you just want to check that a single type of annotation has been applied then you can use findAnnotation which returns any annotation object of the specified type or null if none exist:
Notice that as a nullable type you have to continue to check for null in the returned value, e.g.:
As well as simple bare annotations you can also include some data as Annotation classes can have a primary constructor. For example, you might want to record the name of the programmer who created the entity:
By providing a default value, the user of the annotation doesn't have to supply the information:
You can retrieve the data as a property of the annotation class retrieved by either annotations or findAnnotation:
There are also a range of annotations that you can apply to custom annotations:
The one you are likely to use most often is @Target to restrict the range of entities that the annotation can be applied to. For example:
sets MyAnnotation as usable only on classes. If you try to use it on something other than a class you generate a compiler error message: You can include multiple targets in the @Target annotation. Annotations can include annotations as properties set in their primary constructors and things can become complicated. Kotlin annotations also work with Java annotations. In general you need to think carefully about using annotations. They work well where you need to attach information to classes that isn't going to change at the instance level. You also need to think about the two parts that make an annotation work. There is the annotation class itself which the programmer uses to attach data to the classes and other entities they create. Then there is the processing machinery that consumes the annotations. This machinery is usually part of a framework that the user is allowed to extend by adding classes which have to declare their characteristics to the existing classes. This is generally a big undertaking. In short, custom annotations are not a facility that every project needs to undertake. Summary
This article is an extract from: Programmer's Guide To Kotlin Third Edition
You can buy it from: Amazon Contents
<ASIN:B0D8H4N8SK> To be informed about new articles on I Programmer, sign up for our weekly newsletter, subscribe to the RSS feed and follow us on Twitter, Facebook or Linkedin.
Comments
or email your comment to: comments@i-programmer.info <ASIN:1871962536> <ASIN:1871962544> |
Last Updated ( Monday, 08 January 2018 ) |