When I need to define constants, avoid “magical numbers” and model data in Kotlin and other languages, I’ve usually default to
I was told this was a good practice when I first started coding, and it seemed reasonable, but there are always alternatives to resolve each problem.
Today I want to share what choices exist in Kotlin when we need this kind of behaviour in our code.
Hey, can’t we just use compile-time constants?
In Kotlin, we have two options when it comes to define compile-time constants.
Both depend on the
const val attributes, but can be defined in two different ways.
We can declare a
const val inside a
companion object, or as a top-level variable. Declarations inside classes aren’t allowed.
If you already have a
companion object, I always like to declare my
const valinside it, otherwise, I define it as top-level variable.
This is because when you declare a
companion object you are adding overhead to your code as now you are creating a Singleton object just to declare a constant variable.
But to be honest, I don’t see how this is actually going to affect performance or any other parameters, as it’s such a small thing, even for enormous codebases.
I try to follow this as a “good practice”, but my opinion is that you do whatever suits better your code style and forget about the performance “drawback”.
Be aware that declaring a variable inside a
companion object doesn’t mean that is a compile-time one, you need to explicitly mark it as a
const val, otherwise, its value can be changed at runtime.
This is because here we would be creating a variable associated to an object that has been instantiated, is not exactly like a static variable in Java, but it has the same behaviour.
These approaches have some limitations, which are mainly about flexibility, as they are intended to be super simple.
You can’t define a custom getter for the property, but you can create a function to replicate that behaviour or think about a different approach.
One of them is that
const val are limited to primitive types only. If you need more complexity, you are going to need to use
sealed class or
These classes not only allow the developer to define more complex objects that can work as constants. Now you can implement functions to expand their functionality with
extensions functions even so if you like.
Now let’s talk a little more about these two alternatives, but be aware that these are not compile-time like
const val where.
Let’s start with a brief piece of code of how a
sealed class looks.
They have the property that once they are compiled, they prevent third-party actors from extending them, and they cannot be instantiated, only their children can.
Subclasses used to be only available for declaration in the same file as where the
sealed class was, but now you can do this as long as it’s in the same package.
I think it’s a good idea to keep it all in the same file to be organized, but now you can choose whatever you prefer.
Let’s start again with a simple example.
With this kind of classes, the developer is restricted to just the instances that are defined in the declaration of the
enum class. This means that we only have one instance of each item declared inside the class.
Although this might give the impression that it could be just a compile-time constant, as the set of possibilities of these classes is limited when we compile the program, these classes can also have mutable properties which values could be modified at runtime.
Although it can be done, this is a dangerous approach, as changing the value of a property of an
enum class it’s going to affect its value in others parts of the code where the
enum class is being used, as we are working with Singleton objects as stated before.
Note also that they are compiled at runtime as they are needed, like in Java.
In Kotlin, a
enum class is a Singleton, where a
sealed class can have the option to be defined as a traditional class which can be instantiated several times, meaning each of these classes can have their own state.
As we saw earlier, you can define a
sealed class like an object, and then you would have the same behaviour as with a
Enum and sealed classes work great with the Kotlin
when keyword. You can program it so that it’s exhaustive, this is, when you combine them with
when, the IDE is going to offer you to complete the branches for you, and if one of the options is not inserted, the compiler will fail and you will avoid runtime errors.
It’s also interesting to know that both can implement interfaces if you need to.
If you want to read more content like this without ads and support me, don’t forget to check my profile, or give Medium a chance by becoming a member to access unlimited stories from me and other writers. It’s only $5 a month and if you use this link I get a small commission.