JavaScript week JavaScript week
This week up to 80% off on HTML/CSS and JavaScript courses.

Lesson 9 - Companion objects in Kotlin

In the previous lesson, Arena with a mage in Kotlin (inheritance and polymorphism), we put our knowledge of inheritance and polymorphism to the test. In today's tutorial, we're going to go over static class members in Kotlin. Until now, we only used data, states, being carried by an instance. Properties, which we've defined, belonged to an instance and were unique to each instance. OOP, however, allows us to declare properties and methods on a class itself. We call these members static, sometimes called class members, who are independent of an instance.

Beware of static membersWARNING! Today's lesson will show you that static members are approaches that actually violate the object-oriented principles. OOP includes them only for special cases and in general everything can be written without static members. We always have to think carefully before adding static members. Generally, I would recommend for you not to use static members ever, unless you're absolutely sure what you're doing. Like global variables, static members are something that enable you to write bad code and violate good practices. Today, we'll go over this topic just to make you understand some static methods and classes in Kotlin, not to write your own. Use this knowledge wisely, there will be less evil in the world.

Static (class) properties

We can declare various elements as static, let's start with properties. As I mentioned in the introduction, static elements belong to a class, not to an instance. So the data stored there can be read even if an instance of the class has not been declared. Basically, we could say that static properties are shared among all class instances, but even that wouldn't be accurate since they aren't actually related to instances.

Let's create a new project, name it something like Statics, and create a simple User class:

class User(private val name: String, private val password: String) {

    private var loggedIn = false

    fun logIn(enteredPassword: String) {
        if (enteredPassword == password)
            loggedIn = true
        else
            loggedIn = false
    }
}

The class is simple, it represents a user in a system. Each user instance has its own name, password, and carries information about whether the user is logged in. In order for the user to log-in, we call the logIn() method which takes a password as a parameter. The method verifies whether it's the right password. If the person behind the keyboard is really that user, it logs him/her in. It returns true/false depending on whether the login was successful. In reality, the password would be also hashed, but we won't do any hashing this time around.

When a new user registers, the system tells him/her what the minimum length of their password must be. This number should be stored somewhere. During user registration, we still wouldn't have its instance. The object has not been created and would only be created after the form has been completely filled and submitted. Therefore, we can't use a public minimalPasswordLength property in the User class for this purpose. Either way, we still need a minimum password length stored in the User class somewhere since it, naturally, belongs there. We'll make this value a static property using the companion object block:

class User(private val name: String, private val password: String) {

    private val loggedIn: Boolean

    init {
        loggedIn = false
    }

    // the rest of the implementation...

    companion object {
        val minimalPasswordLength = 6
    }
}

Now, let's switch to Main.kt and print the property to the console. We access this property directly on the class:

fun main(args: Array<String>) {
    println(User.minimalPasswordLength)
}

As said before, Java can use Kotlin classes. If we wanted to use static variables declared in Kotlin from Java, we'd first have to mark each of them with the @JvmStatic annotation, i.e. write this text above it. However, because we only write code for Kotlin, we won't use this feature. . [hint]

We can see the property really belongs to the class. We can access it from different places in the code without having to create a user. On the contrary, we wouldn't be able to access it on the user instance:

// this code will result in error
val u = User("Thomas White", "passwordissword");
println(u.minimalPasswordLength)

IntelliJ will report an error and the code won't be compiled.

Numbering the instances

Another practical use of static properties is to assign unique identification numbers to each user. If we didn't know what static members were, we'd have to check every user creation and increment a counter. Instead, we'll create a private static property nextId right on the User class. The next user who registers will have their id stored there. The first user's id will be 1, the second 2, and so on. The User will get a new attribute - id, that will be set in the constructor depending on what the nextId value is. Let's try it:

class User(private val name: String, private val password: String) {

    private var loggedIn: Boolean
    val id: Int

    init {
        loggedIn = false
        id = nextId
        nextId++
    }

    // the rest of the implementation...

    companion object {
        private var nextId = 1
        val minimalPasswordLength = 6
    }
}

The class stores the next instance's id by itself. We assign this id to a new instance in the constructor and increase it by 1, which prepares it for the next instance. Not only properties can be static, this approach could be applied to a variety of class members.

Static methods

Static methods are called on the class. All they usually are is utilities that we need to use often and creating an instance every time would be counterproductive. Kotlin does support so-called functions which aren't called on an instance nor class (this is how we use the println() function), however, it's much more useful to gather related functions in classes. That way, IntelliJ will autocomplete them for us better and we'll be able to open the list of everything we can call on a particular class by pressing Ctrl + Space.

Let's make another example, just to clarify. During user registration, we need to know the minimum password length before we create its instance. Also, it would be great if we could check the password before the program creates the user. A full check, in this case, would include checking whether the length is correct, making sure it doesn't contain accent characters, verifying that there is at least one number in it, and so on. To do this, we'll create a static validatePassword() utility method:

companion object {
    private var nextId = 1
    val minimalPasswordLength = 6

    fun validatePassword(password: String): Boolean {
        if (password.length >= minimalPasswordLength)
            return true
        else
            return false
    }
}

Let's try to call the method on the User class:

println(User.validatePassword("passwordissword"))

Viewer beware! The validatePassword() method belongs to the class. Meaning that we can't access any instance properties in it. Those properties don't exist in the class context, rather in the context of an instance. It wouldn't make any sense to use the user's name in our method! You can try it out if you'd like to get a visual confirmation of its impossibility.

Password validation can be achieved without knowledge of static members. We could create a UserValidator class and write methods in it accordingly. Then, we'd have to create its instance to be able to call those methods. It'd be a bit confusing because the "concept of a user" would be unnecessarily split up into two classes. Now, thanks to static members, all of the information is neatly organized in one place.

We didn't break encapsulation in the example with the static minimalPasswordLength property because we declared the property as val.

Finally, we'll test our static properties. The main() method will look like this:

val u = User("Thomas White", "passwordissword")
println("First user's ID: ${u.id}")
val v = User("Oli Pickle", "csfd1fg")
println("Second user's ID: ${v.id}")
println("Minimum password length: ${User.minimalPasswordLength}")
println("""Validity of password "password" is ${User.validatePassword("password")}""")

Output screenshot:

First user's ID: 1
Second user's ID: 2
Minimum password length: 6
Validity of password "password": false

Private constructor

If a class contains only utility methods or it doesn't make sense to create an instance of it, for example we'll never run two consoles at a time, we call it static class/object. Kotlin lets us to mark the class as static by using the object keyword. This object is then not possible to instantiate (we can't create an instance of such class).

Let's create a test object A which we won't be able to instantiate. We'll replace class by the object keyword:

object A {
    // Here we could write our methods and properties
}

Let's try to instantiate the A object:

val a = A()

We'll get an error because instantiation is prohibited. All properties of the object are static and therefore it doesn't make sense to create an instance of it; it wouldn't contain anything.

Kotlin also supports the static constructor which is called when the class is registered. We create it the same way as a class constructor, only in object.

object A {
    init {
        // Here would be our static construction code
    }
    // Here we could write our methods and properties
}

If we wanted to have such constructor in a regular class and initialize static variables in it, we can do so:

class Class {
    companion object {
        init {
            // Here would be our static construction code
        }
        // Here we could write our methods and properties
    }
}

Static registry

Let's create a simple static class. We could create a class containing only utility methods and properties. However, I've decided I'll have us create a static registry. Which is basically how you share important data between classes without making an instance.

Let's say we're making a more robust application, e.g. a digital journal. The application would be multilingual, its features would include adding bookmarks, choosing folders for storing files, color schemes and even running at system startup. It would have adjustable settings, e.g. picking the first day of the week (Sunday/Monday), which would be accessed from various parts in the code. Without knowledge of static members, we'd have to pass the settings instance to all objects (calendar, tasks, notes...) through the constructor.

One workaround would be to use an object to store these settings. It'd be accessible everywhere in the program, without even having to create an instance. It'd contain all the necessary settings that would be used by other objects. Our object would end up looking something like this:

object Settings {
    val language = "ENG"
    val colorScheme = "red"
    val runAtStartUp = true
}

Now we'll put our class to use even though we don't have a full journal application. Let's make a Calendar class and verify that we can truly access Settings there. We'll add a method to it that returns all of the settings:

class Calendar {
    fun getSettings(): String {
        return "Language: ${Settings.language}\n" +
               "Color scheme: ${Settings.colorScheme}\n" +
               "Run at start-up: ${Settings.runAtStartUp}"
    }
}

Last of all, we'll print it all to the console:

val calendar = Calendar()
println(calendar.getSettings())

The output:

Language: ENG
Color scheme: red
Run at start-up: true

We see that calendar instance has no problem accessing all program settings.

Again, be careful, this code can be improperly used to share unencapsulated data. We must only use it in specific situations. Most data exchanges should happen using parameterized instance constructors, not through static members.

Static members appear very often in design patterns, we've already mentioned them in our lessons. We'll get in further detail in the approaches that bring object-oriented programming to perfection later on. For now, this will do. I don't want to overwhelm you :) In the next lesson, Properties in Kotlin, we'll look at what properties are in Kotlin.


 

 

Article has been written for you by Samuel Kodytek
Avatar
Do you like this article?
No one has rated this quite yet, be the first one!
I'm intereseted in JVM languages and Swift
Previous article
Arena with a mage in Kotlin (inheritance and polymorphism)
All articles in this section
Object-oriented programming in Kotlin
Thumbnail
Next article
Properties in Kotlin
Activities (2)

 

 

Comments

To maintain the quality of discussion, we only allow registered members to comment. Sign in. If you're new, Sign up, it's free.

No one has commented yet - be the first!