Blog Infos
Author
Published
Topics
, ,
Published

Kotlin is a very concise language, but when its code is compiled into Java bytecode, Kotlin’s elegant constructs disintegrate into Java’s monstrous constructs. At the same time, the use of annotations can play a cruel joke on you.

Let’s consider an example of @JsonProperty annotation and a regular data class. Let’s suppose that we have a data class like this

data class SomeData(@JsonProperty("id") val someDataId: String)

From the code it is logical to assume that the @JsonProperty annotation will be applied to the field someDataId, because we specify it for the field. Thus, our Json file should contain the “id” field instead of “someDataId” and it should work when reading and writing the file.
But is it so? Let’s look into it.

If we decompile Kotlin code, we will see that in Java this class looks like this:

public static final class SomeData {
     @NotNull
    // private field
     private String someDataId; 

     @NotNull
    // field getter
     public final String getSomeDataId() { 
        return this.someDataId;
     }

     // constructor with field param
     public SomeData(@NotNull String someDataId) { 
        Intrinsics.checkNotNullParameter(someDataId, "someDataId");
        super();
        this.someDataId = someDataId;
     }

As you can see, our someDataId field has been converted into a private field, a getter for the field, and a class constructor parameter. So which of these will the annotation be applied to?

If we look at the definition of the @JsonProperty annotation, we see that it can be applied to a private field, a method and a method parameter. That is, to all Java constructs into which our class data field is decomposed.

@Target({ElementType.ANNOTATION_TYPE, 
      ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotation
public @interface JsonProperty

This results in a non-obviousness for the Kotlin compiler. It can apply our annotation to a field, a getter, and a constructor parameter. What do you think it will apply the annotation to in this particular case?

The correct answer is to the constructor parameter of the class.

The real bytecode will look like this and it clearly shows that our annotation applies only to the constructor parameter of the class:

public static final class SomeData {
      @NotNull
      private String someDataId;

      @NotNull
      public final String getSomeDataId() {
         return this.someDataId;
      }

      public SomeData(@JsonProperty("id") @NotNull String someDataId) {
         Intrinsics.checkNotNullParameter(someDataId, "someDataId");
         super();
         this.someDataId = someDataId;
      }

As a result, our class will be read correctly from Json, but Json generation by our class will not work correctly. When writing to Json, the field name will be “someDataId” instead of the expected “id”.

Let’s look into why Kotlin works this way.

Rules for applying annotations in Kotlin

To resolve such collisions in Kotlin, there is a special rule that says:

If an annotation can be applied to several language constructs at once, it will be applied by default to the first matching use-site from the list:

– Method Parameter

– Property (not available in Java)

– Field

And in order to explicitly resolve such collisions at the code level, Kotlin has a special syntax that allows you to explicitly specify the scope of the annotation.

@[use-site]:Annotation

Examples of specifying the scope of an annotation:

class Example(
    @field:JsonProperty("Foo") val foo,    // annotate Java field
    @get:JsonProperty("Bar") val bar,      // annotate Java getter
    @param:JsonProperty("Some") val some   // annotate Java constructor parameter
)

Here is a complete list of use-site types taken from the Kotlin documentation

· file

· property (annotations with this target are not visible to Java)

· field

· get (property getter)

· set (property setter)

· receiver (receiver parameter of an extension function or property)

· param (constructor parameter)

· setparam (property setter parameter)

· delegate (the field storing the delegate instance for a delegated property)

Conclusions

To make our class work properly, we need to specify the annotation like this:

data class SomeData(@field:JsonProperty("id") var someDataId: String)

Or even better, like this to remove the use of reflection to access a private field

data class SomeData(
    @param:JsonProperty("id")
    @get:JsonProperty("id")
    var someDataId: String
)

You may never encounter this problem, because usually the scope of annotations is chosen to avoid such collisions. This explains the fact that most Kotlin developers are unaware of this syntax and the ability to explicitly specify the scope of annotations.

But it’s useful to know this so that you don’t look at the solution to your StackOverflow problem as magic, but rather understand how it works and what makes it work.

Study the bytecode that Kotlin generates. It will give you a whole different level of understanding of the language and how your code works.

If you are interested in how Kotlin works under the hood, you can read my other articles about Kotlin.

This article was previously published on proandroiddev.com

Job Offers

Job Offers

There are currently no vacancies.

OUR VIDEO RECOMMENDATION

Jobs

YOU MAY BE INTERESTED IN

YOU MAY BE INTERESTED IN

blog
It’s one of the common UX across apps to provide swipe to dismiss so…
READ MORE
blog
Hi, today I come to you with a quick tip on how to update…
READ MORE
blog
Automation is a key point of Software Testing once it make possible to reproduce…
READ MORE
blog
Drag and Drop reordering in Recyclerview can be achieved with ItemTouchHelper (checkout implementation reference).…
READ MORE

Leave a Reply

Your email address will not be published. Required fields are marked *

Fill out this field
Fill out this field
Please enter a valid email address.

Menu