Property Order in Kotlin – Why It Matters
Writing Android apps in Kotlin is nice, as it’s easy to learn, especially when we know Java. Yet there are some traps we can fall into. Today, I would like to show you one related to Kotlin properties. We will also look into the bytecode.
Order of Class Properties
Actually, in most cases, tools embedded in IDEs like Android Studio or Intellij are very helpful but sometimes they can mislead us. Let’s assume that we have the following class:
It’s quite simple, isn’t it? But there is a snag in this code, because when we call println(Foo().product)
we will get 0
instead of 2
. Although isValid
seems to be true
, in actuality when the product
is evaluated, the isValid
property isn’t initialized yet and returns false
, so number
returns 0
.
Even the lint shows us a misleading hint. When we make a quickfix using Alt+Enter and selecting Simplify expression
, we will get get() = 1
so the code behavior will change.
By moving the isValid
above the product
property we will get the expected result. It’s obvious now but we have to remember that properties order does matter and sometimes it can cause a lot of problems during refactoring.
Show Kotlin Bytecode!
But let’s go deeper. In the Intellij/Android Studio, we have a very useful tool which can show us the generated bytecode:
What is even more interesting is the Decompile
button, which is very helpful when we want to learn Kotlin by analyzing how the equivalent code written in Java would appear.
Let’s take our first example from this article and generate a decompiled version:
When we save this as a new Java class and call println(Foo().product)
we will get 2
, which is different from the first result printed by Kotlin code. So, are these 2 code samples compiled to the same bytecode? Obviously not. The Decompile
button takes bytecode generated by Kotlin and tries to decompile it to regular Java code. So where does the difference come from? Let’s compare them! The simplest way to do this is to compile Foo.java
and Foo.kt
using CLI compilers.
First, we should create 2 directories, for example kotlin
and java
, and place the Foo.kt
and Foo.java
there, respectively. Now, in each folder we should compile our sources to class files and use the javap
tool to show bytecode.
When we compare these 2 generated files, we will detect that there are 2 noticeable differences. But the important place is function getNumber
.
As we can see, the bytecode of Java class in the getNumber
function uses iconst_1
, which loads value 1
onto the stack. But, in the case of Kotlin code, ifeq
checks whether isValid
field is 0
(false) and, if so, it goes to iconst_0
, otherwise it goes to iconst_1
. Finally 0
or 1
is returned respectively.
Let’s break something
So, we know now that the Decompile
option can show us Java code which gives us a different result than our Kotlin code. However, can we generate Java code which doesn’t compile? Of course! Let’s consider this case:
The sample above is 100% correct Kotlin code which compiles to regular JVM bytecode:
In this example, we use backquotes to name a property using spaces. But, when we choose the Decompile
option in the tool mentioned above, we get the output which isn’t proper Java code, because Java doesn’t allow you to use any spaces in the fields. Moreover, the generated method is_valid()
is repeated, which is also incorrect.
Conclusion
Is worth remembering that we should always be careful about property order in Kotlin, but also be aware of how tools embedded in our IDE work. Although both Java and Kotlin are JVM languages, not every JVM bytecode can be decompiled to compilable Java code.
Ready to take your business to the next level with a digital product?
We'll be with you every step of the way, from idea to launch and beyond!