Every piece of code in any codebase has to work with some values, those values come in many forms/syntax, Some of them are variables that vary over time and some others are just constants. the latter isn’t something new in programming languages, it has been addressed by early programming languages designers and Hence they provided first-class support for declaring constants.
In Kotlin we have 3 main different options to declare a constant. One of those options is keeping the constants shared in one
object, this is simple enough and doesn’t have any bad consequences for usability, maintainability or performance but this is only suitable for global constants, So in order to create some locally scoped constants, we have to go with the other 2 options. those 2 options created some confusion for us as developers on When to use option X over option Y, so I’m gonna talk about those 2 options and a third option which is creating constants with just a
const modifier from usability and performance perspectives.
1. First Option: Declare Constants as a top-level values in the same file
As kotlin is a functional programming language unlike Java, we can declare top-level functions, values directly without needing for a class to wrap them, So we do some thing like this:
But anyway this is gonna be converted to Java bytecode when compiled, So how Kotlin tell Java about top-level constants because Java doesn’t have such a concept, If we look at the decompiled bytecode, we will see:
Kotlin will create a class with the name of our file for us and all of the constants will be marked as
static , So when invoking a constant from those no object allocation will happen or unnessacary object creation, Thus we see that this option has no overhead at runtime, but wait before you judge we still don’t know about other options, Also some people(including me in some situations) will not prefer this option because declaring constant as a top-level sometimes doesn’t provide a strong indication (compared to other options) that it is related to class/functions defined in the same source file.
2. Second Option: Declare constants in a companion object
This the most common way and the first thing that comes to your mind when you need to declare a constant, We do that like this:
if We look at the decompiled bytecode this time:
Similar to what we saw before but this time with a new class called
Companion created for us and also a new instance of it created automatically, in comparison with the previous approach we can see the previous one outperforms this approach from a performance perspective because of the new unnecessary object allocated But on the other hand this approach represent the constants in a more structured and semantic way compared to the previous one.
NOTE: Using R8 with optimization enabled can eliminate/remove this unnecessary object allocation and move the field/method to outer class (i.e
Wrapper) plus making it a static instead of member/virtual property. This Process is called Staticization.
If you haven’t heard of this term before? check R8 Optimization: Staticization by Jake Wharton
We can now easily choose what is more convenient For us, So are we done yet? No, there is third option we haven’t covered yet.
3. Third Option: Declaring constant with just
We all know that
val is the equivalent of
final in java so it can be used for declaring constants as well but without using
const modifier, actually this will be the only option if we have to declare something that is not primitive or String, but can we compare that option to other options in terms of performance, Let’s see:
If we look at the decompiled bytecode:
As you can see, nothing fancy is happening here. it is merely identical to the decompiled bytecode of 2 previous approaches. But let’s make a small change: we will change the access modifier of the field from
will it differ from the previous? Let’s see:
And the decompiled bytecode:
similar to the previous, but this time with a
public getFoo() generated for us and changing our
public field to
private one, So we can access the field through its getter method, and that may annoy some of you who still think accessing getter method is more expensive than accessing the field directly.
Fortunately, Android Runtime is smart enough to inline trivial getter method like the one we have above, So you don’t get any performance hit. see this article for details.
Moreover, not just the ART that is smart, R8/Proguard also could inline(Assuming optimization is enabled) that method for you as well. So if you don’t believe yet in ART optimization, you will have R8 do it for you.
So, what to do now?
Basically, I have shown you the different options and shared my humble opinion with you. Personally, I prefer approach #2. But in the end, choose whichever convenient for your situation/use case.