Kotlin Under the hood: the magic of classes. Part 2
What’s up guys! Last week we were talking about field initialization logic, data classes, getters, and setters. If you have not read it — start from the previous article:
Today we gonna talk about multiple constructors in a class, open and sealed classes, and lateinit modifier.
Example #1: Multiple constructors
As you’ve seen in the previous part — Kotlin tries to make our class smaller and the primary constructor flattens with the class name. Look at the example:
Also, if we want to specify primary constructor visibility or annotations, etc, we add the constructor keyword:
Constructor visibility can be private, internal, public(default behavior), protected.
So, let’s create additional constructors:
If we have a primary constructor you MUST call it from the secondary constructor. It doesn’t matter which visibility secondary constructor has.
Example #2: Init block
Each constructor may have its own initialization block or use init block which is shared to all constructors:
In Java it will look like this:
So, our primary constructor has
System.out.println as well as secondary because it calls the primary constructor explicitly.
To avoid this we can create custom initialization block for the secondary constructor and delete init block:
And Java analog:
System.out.println() added only inside the secondary constructor. Great!
Example #3: Open classes
In Kotlin all classes and methods are
public final by default. You might have seen it in previous examples. So, as it’s
final we can’t override it. To deal with this issue
open keyword was introduced.
To extend class Car we added the
open modifier to it. To extend class we call its constructor after a colon.
As we see
Mercedes is final but not
Car as it is
final as it’s
val numberOfDoors is not
Let’s fix it:
open as it creates method
final by default as any other method in Kotlin class.
final as it’s marked as
Example #4: Sealed classes
On the first glance
sealed classes are similar to
enumclasses. The difference is quite obvious —
enum class is a group of related constants,
sealed class is a group of related classes with a different state.
In the first versions of Kotlin sealed classes should have their children inside them(kind of inner classes). For now, this restriction is not a mandatory requirement. Also, I should mention that a sealed class is implicitly
You can use sealed classes if you have a constrained amount of options with different values in it. Every
enum constant available in only one instance,
sealed classhowever, can have multiple instances with the different values in it.
So, the modern sealed class may look like this:
This example is probably the most obvious. We put value or error depending on the server response and treat the
Result value depending on the type. For Java developers I’ve prepared the decompiled code with some metadata:
As you can see
Result is abstract. Metadata identifies all possible instances of the sealed class:
Error. It will help us to use
Result types inside the
when supports sealed classes and hint you that you should cover all branches(types)while using it in a
return statement. As I’ve mentioned that metadata knows all the types of the
Result, so it can find the missing branch.
In the example above we used
data classes but you can use any class as a child of
Example #5: lateinit
lateinit indicates that property value will be set later. You promise it and if it won’t be done — you will get an exception.
lateinit works only with non-primitive types.
lateinit is useful when you don’t want to mark this field nullable as it never gets null value but you don’t have enough information to init property during declaration. Accessing property before it has been initialized will throw an exception.
As we promise to initialize it later we can’t use
val together with
lateinit as it’s actually mutable property and we can’t use
lateinit with constructor parameters as it should be initialized later, not in constructor.
Let me show a little example:
And now let’s check what’s under the hood, why and when it throws exceptions:
getHorsePower() throws an exception if the property is
null (not initialized yet). If we have a function which uses
horsePower property, we will get the exception. To avoid this we can do the following:
isInitialized help us to use
doThings safely and not to call getter when the property is not initialized to avoid exceptions.
You may notice that
horsePower. It’s a member reference — it returns not the field itself but class with property metadata —
isInitializedis an extension function for
KPropertywhich checks property value.
Take a look at
doThings method — it just checks if the value is not
null at the moment.