For as long as there have been Android devices, there have been Android developers programming in Java. Or at least, that was the case until 2011 when Jetbrains announced they were developing a new statically typed programming language for the JVM. And if it’s good enough for the JVM then it’s good enough for Android!
But why build a new language when Java is so dandy?
“[We] believe that the community can benefit from a new statically typed JVM-targeted language free of the legacy trouble and having the features so desperately wanted by the developers.”
While Kotlin does make good on its promise to offer Android developers a modern language, it is not all sunshine and roses. Kotlin giveth, and it also taketh away. One example is static constants. In Java, classes can have static constants - fields that are only created once per class (as opposed to once per object), cannot be changed (without recompiling), and can be accessed without an instance of their containing class. Static constants are a great tool for developers as they allow us to share the same variable (both in value and in memory location) across all instances of a given class. While the memory storage is a nice bonus, the real advantage is in their ability to encode meaning into an otherwise random variable. Consider the following:
private static final int MAX_ALPHA = 255;
Because the static constant we are defining is a primitive type, the compiler
will replace our constant name everywhere with its value, in this case 255
, at
compile time. So there is absolutely no reason for us to not just write 255
everywhere we use that constant. Well, no reason, except that 255 on it’s own is
a magic number
which someone else, might, very reasonably, not know happens to be the maximum
value representable by an eight-digit binary number and therefore represents the
maximum value that can be assigned to elements in the 24-bit RGBA color model,
since each color channel is allotted eight bits. But I digress.
All of that (totally) unnecessary (but hopefully informative) mathematic malarky was to make the point that static constants are valuable and can be very helpful in making code more readable.
But why does any of that matter in terms of Kotlin? Well in Kotlin, we don’t have static constants anymore.
The official Kotlin documentation recommends the following alternatives:
If you need to write a function that can be called without having a class instance … you can write it as a member of an object declaration inside that class.
Even more specifically, if you declare a companion object inside your class, you’ll be able to call its members with the same syntax as calling static methods in Java/C#, using only the class name as a qualifier.
If you read that and thought, “companion objects are for me! They’ll make my Kotlin code can look just like my Java code and, just as Bob Marley predicted, ‘every little thing gonna be alright’!” Then you are just like me. And sadly, just like me, you would be wrong. Every little thing not gonna be alright. While companion objects do offer us the same syntax as our dear Java static constants, they are not the same thing.
Consider how our earlier static constant would look in Kotlin:
companion object {
private val MAX_ALPHA: Int = 255
}
Looks pretty similar right?. Well, as the title of this blog post suggests,
companion objects are objects too. Which means that to get at that MAX_ALPHA
we would actually have to create an object first, then access it’s member
variable. And object creation is expensive. And let me tell you, that integer is
not worth the cost. How do I know? Because my Android trace
profile told me of
course!
Don’t believe me? Let’s take a look at some profiles. Consider a custom view
that uses our MAX_ALPHA
value on each onDraw
call.
Here’s what the profile looks if we use a companion object:
I have highlighted the row fetching the MAX_ALPHA
variable from the companion
object class.
Now here is what the trace looks like if we just place the MAX_ALPHA
as just a
class variables:
You will notice that total CPU Time % for the same drawDiagonalLines
method
went from 18.7% with a companion object, to 11.1% without it! Do you believe me
now?
For the purposes of this exmaple, replacing a companion object with a class variable was an acceptable solution, but if that won’t work for you, and you need a constant with a broader scope, you can use package-level functions.
Regardless of what soltuion you go with, as long as you remember that companion objects are objects too, and not the same as Java’s static constants, then, maybe, just maybe, “every little thing is gonna be alright”