Refactoring to Kotlin

In this codelab, you'll learn how to convert your code from Java to Kotlin. You'll also learn what the Kotlin language conventions are and how to ensure that the code you're writing follows them.

This codelab is suited to any developer that uses Java who is considering migrating their project to Kotlin. We'll start with a couple of Java classes that you'll convert to Kotlin using the IDE. Then we'll take a look at the converted code and see how we can improve it by making it more idiomatic and avoid common pitfalls.

What you'll learn

You will learn how to convert Java to Kotlin. In doing so you will learn the following Kotlin language features and concepts:

  • Handling nullability
  • Implementing singletons
  • Data classes
  • Handling strings
  • Elvis operator
  • Destructuring
  • Properties and backing properties
  • Default arguments and named parameters
  • Working with collections
  • Extension functions
  • Top-level functions and parameters
  • let, apply, with, and run keywords

Assumptions

You should already be familiar with Java.

What you'll need

Create a new project

If you're using IntelliJ IDEA, create a new Java project with Kotlin/JVM.

If you're using Android Studio, create a new project with no Activity.

The code

We'll create a User model object and a Repository singleton class that works with User objects and exposes lists of users and formatted user names.

Create a new file called User.java under app/java/<yourpackagename> and paste in the following code:

public class User {

    @Nullable
    private String firstName;
    @Nullable
    private String lastName;

    public User(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

}

Depending on your project type, import androidx.annotation.Nullable if you use an Android project, or org.jetbrains.annotations.Nullable otherwise.

Create a new file called Repository.java and paste in the following code:

import java.util.ArrayList;
import java.util.List;

public class Repository {

    private static Repository INSTANCE = null;

    private List<User> users = null;

    public static Repository getInstance() {
        if (INSTANCE == null) {
            synchronized (Repository.class) {
                if (INSTANCE == null) {
                    INSTANCE = new Repository();
                }
            }
        }
        return INSTANCE;
    }

    // keeping the constructor private to enforce the usage of getInstance
    private Repository() {

        User user1 = new User("Jane", "");
        User user2 = new User("John", null);
        User user3 = new User("Anne", "Doe");

        users = new ArrayList();
        users.add(user1);
        users.add(user2);
        users.add(user3);
    }

    public List<User> getUsers() {
        return users;
    }

    public List<String> getFormattedUserNames() {
        List<String> userNames = new ArrayList<>(users.size());
        for (User user : users) {
            String name;

            if (user.getLastName() != null) {
                if (user.getFirstName() != null) {
                    name = user.getFirstName() + " " + user.getLastName();
                } else {
                    name = user.getLastName();
                }
            } else if (user.getFirstName() != null) {
                name = user.getFirstName();
            } else {
                name = "Unknown";
            }
            userNames.add(name);
        }
        return userNames;
    }
}

Our IDE can do a pretty good job of automatically refactoring Java code into Kotlin code but sometimes it needs a little help. We'll do this first and then go through the refactored code to understand how and why it has been refactored this way.

Go to the User.java file and convert it to Kotlin: Menu bar -> Code -> Convert Java File to Kotlin File.

If your IDE prompts for correcting after conversion, press Yes.

You should see the following Kotlin code: :

class User(var firstName: String?, var lastName: String?)

Note that User.java was renamed to User.kt. Kotlin files have the extension .kt.

In our Java User class we had two properties: firstName and lastName. Each had a getter and setter method, making its value mutable. Kotlin's keyword for mutable variables is var, so the converter uses var for each of these properties. If our Java properties had only getters, they would be immutable and would have been declared as val variables. val is similar to the final keyword in Java.

One of the key differences between Kotlin and Java is that Kotlin explicitly specifies whether a variable can accept a null value. It does this by appending a `?` to the type declaration.

Because we marked firstName and lastName as nullable, the auto-converter automatically marked the properties as nullable with String?. If you annotate your Java members as non-null (using org.jetbrains.annotations.NotNull or androidx.annotation.NonNull), the converter will recognize this and make the fields non-null in Kotlin as well.

The basic refactoring is already done. But we can write this in a more idiomatic way. Let's see how.

Data class

Our User class only holds data. Kotlin has a keyword for classes with this role: data. By marking this class as a data class, the compiler will automatically create getters and setters for us. It will also derive the equals(), hashCode(), and toString() functions.

Let's add the data keyword to our User class:

data class User(var firstName: String, var lastName: String)

Kotlin, like Java, can have a primary constructor and one or more secondary constructors. The one in the example above is the primary constructor of the User class. If you're converting a Java class that has multiple constructors, the converter will automatically create multiple constructors in Kotlin as well. They are defined using the constructor keyword.

If we want to create an instance of this class, we can do it like this:

val user1 = User("Jane", "Doe")

Equality

Kotlin has two types of equality:

  • Structural equality uses the == operator and calls equals() to determine if two instances are equal.
  • Referential equality uses the === operator and checks if two references point to the same object.

The properties defined in the primary constructor of the data class will be used for structural equality checks.

val user1 = User("Jane", "Doe")
val user2 = User("Jane", "Doe")
val structurallyEqual = user1 == user2 // true
val referentiallyEqual = user1 === user2 // false

In Kotlin, we can assign default values to arguments in function calls. The default value is used when the argument is omitted. In Kotlin, constructors are also functions, so we can use default arguments to specify that the default value of lastName is null. To do this, we just assign null to lastName.

data class User(var firstName: String?, var lastName: String? = null)

// usage
val jane = User("Jane") // same as User("Jane", null)
val joe = User("John", "Doe")

Function parameters can be named when calling functions:

val john = User(firstName = "John", lastName = "Doe") 

As a different use case, let's say that the firstName has null as its default value and lastName does not. In this case, because the default parameter would precede a parameter with no default value, you would have to call the function with named arguments:

data class User(var firstName: String? = null, var lastName: String?)

// usage
val jane = User(lastName = "Doe") // same as User(null, "Doe")
val john = User("John", "Doe")

Before going forward, make sure that your User class is a data class. Let's convert the Repository class to Kotlin. The automatic conversion result should look like this:

import java.util.*

class Repository private constructor() {
   private var users: MutableList<User?>? = null
   fun getUsers(): List<User?>? {
       return users
   }

   val formattedUserNames: List<String?>
       get() {
           val userNames: MutableList<String?> =
               ArrayList(users!!.size)
           for (user in users) {
               var name: String
               name = if (user!!.lastName != null) {
                   if (user!!.firstName != null) {
                       user!!.firstName + " " + user!!.lastName
                   } else {
                       user!!.lastName
                   }
               } else if (user!!.firstName != null) {
                   user!!.firstName
               } else {
                   "Unknown"
               }
               userNames.add(name)
           }
           return userNames
       }

   companion object {
       private var INSTANCE: Repository? = null
       val instance: Repository?
           get() {
               if (INSTANCE == null) {
                   synchronized(Repository::class.java) {
                       if (INSTANCE == null) {
                           INSTANCE =
                               Repository()
                       }
                   }
               }
               return INSTANCE
           }
   }

   // keeping the constructor private to enforce the usage of getInstance
   init {
       val user1 = User("Jane", "")
       val user2 = User("John", null)
       val user3 = User("Anne", "Doe")
       users = ArrayList<Any?>()
       users.add(user1)
       users.add(user2)
       users.add(user3)
   }
}

Let's see what the automatic converter did:

  • An init block was added (Repository.kt#L50)
  • The static field is now part of a companion object block (Repository.kt#L33)
  • The list of users is nullable since the object wasn't instantiated at declaration time (Repository.kt#L7)
  • The getFormattedUserNames() method is now a property called formattedUserNames (Repository.kt#L11)
  • The iteration over the list of users (that was initially part of getFormattedUserNames() ) has a different syntax than the Java one (Repository.kt#L15)

Before we go further, let's clean up the code a bit. We can see that the converter made our users list a mutable list that holds nullable objects. While the list can indeed be null, let's say that it can't hold null users. So let's do the following:

  • Remove the ? in User? within the users type declaration
  • getUsers should return List<User>?

The auto-converter also unnecessarily split into 2 lines the variable declarations of the users variables and of the ones defined in the init block. Let's put each variable declaration on one line. Here's how our code should look like:

class Repository private constructor() {
    private var users: MutableList<User>? = null

    fun getUsers(): List<User>? {
        return users
    }

    val formattedUserNames: List<String?>
        get() {
            val userNames: MutableList<String?> =
                ArrayList(users!!.size)
            for (user in users) {
                var name: String
                name = if (user!!.lastName != null) {
                    if (user!!.firstName != null) {
                        user!!.firstName + " " + user!!.lastName
                    } else {
                        user!!.lastName
                    }
                } else if (user!!.firstName != null) {
                    user!!.firstName
                } else {
                    "Unknown"
                }
                userNames.add(name)
            }
            return userNames
        }

    companion object {
        private var INSTANCE: Repository? = null
        val instance: Repository?
            get() {
                if (INSTANCE == null) {
                    synchronized(Repository::class.java) {
                        if (INSTANCE == null) {
                            INSTANCE =
                                Repository()
                        }
                    }
                }
                return INSTANCE
            }
    }

    // keeping the constructor private to enforce the usage of getInstance
    init {
        val user1 = User("Jane", "")
        val user2 = User("John", null)
        val user3 = User("Anne", "Doe")
        users = ArrayList<Any?>()
        users.add(user1)
        users.add(user2)
        users.add(user3)
    }
}

Init block

In Kotlin, the primary constructor cannot contain any code, so initialization code is placed in init blocks. The functionality is the same.

class Repository private constructor() {
    ...
    init {
        val user1 = User("Jane", "")
        val user2 = User("John", null)
        val user3 = User("Anne", "Doe")
        users = ArrayList<Any?>()
        users.add(user1)
        users.add(user2)
        users.add(user3)
    }
}

Much of the init code handles initializing properties. This can also be done in the declaration of the property. For example, in the Kotlin version of our Repository class, we see that the users property was initialized in the declaration.

private var users: MutableList<User>? = null

Kotlin's static properties and methods

In Java, we use the static keyword for fields or functions to say that they belong to a class but not to an instance of the class. This is why we created the INSTANCE static field in our Repository class. The Kotlin equivalent for this is the companion object block. Here you would also declare the static fields and static functions. The converter created and moved the INSTANCE field here.

Handling singletons

Because we need only one instance of the Repository class, we used the singleton pattern in Java. With Kotlin, you can enforce this pattern at the compiler level by replacing the class keyword with object.

Remove the private constructor and the companion object and replace the class definition with object Repository.

object Repository {

    private var users: MutableList<User>? = null

    fun getUsers(): List<User>? {
       return users
    }

    val formattedUserNames: List<String>
        get() {
            val userNames: MutableList<String> =
                ArrayList(users!!.size)
        for (user in users) {
            var name: String
            name = if (user!!.lastName != null) {
                if (user!!.firstName != null) {
                    user!!.firstName + " " + user!!.lastName
                } else {
                    user!!.lastName
                }
            } else if (user!!.firstName != null) {
                user!!.firstName
            } else {
                "Unknown"
            }
            userNames.add(name)
       }
       return userNames
   }

    // keeping the constructor private to enforce the usage of getInstance
    init {
        val user1 = User("Jane", "")
        val user2 = User("John", null)
        val user3 = User("Anne", "Doe")
        users = ArrayList<Any?>()
        users.add(user1)
        users.add(user2)
        users.add(user3)
    }
}

When using the object class, we just call functions and properties directly on the object, like this:

val users = Repository.users

Destructuring

Kotlin allows destructuring an object into a number of variables, using a syntax called destructuring declaration. We create multiple variables and can use them independently.

For example, data classes support destructuring so we can destructure the User object in the for loop into (firstName, lastName). This allows us to work directly with the firstName and lastName values. Let's update the for loop like this:

 
for ((firstName, lastName) in users!!) {
       val name: String?

       if (lastName != null) {
          if (firstName != null) {
                name = "$firstName $lastName"
          } else {
                name = lastName
          }
       } else if (firstName != null) {
            name = firstName
       } else {
            name = "Unknown"
       }
       userNames.add(name)
}

When converting the Repository class to Kotlin, the automatic converter made the list of users nullable, because it wasn't initialized to an object when it was declared. For all the usages of the users object, the not-null assertion operator !! is used. It converts any variable to a non-null type and throws an exception if the value is null. By using !!, you're risking exceptions being thrown at runtime.

Instead, prefer handling nullability by using one of these methods:

  • Doing a null check ( if (users != null) {...} )
  • Using the elvis operator ?: (covered later in the codelab)
  • Using some of the Kotlin standard functions (covered later in the codelab)

In our case, we know that the list of users doesn't need to be nullable, since it's initialized right after the object is constructed, so we can directly instantiate the object when we declare it.

When creating instances of collection types, Kotlin provides several helper functions to make your code more readable and flexible. Here we're using a MutableList for users:

private var users: MutableList<User>? = null

For simplicity, we can use the mutableListOf() function, provide the list element type, remove the ArrayList constructor call from the init block, and remove the explicit type declaration of the users property.

private val users = mutableListOf<User>()

We also changed var into val because users will contain an immutable reference to the list of users. Note that the reference is immutable, but the list itself is mutable (you can add or remove elements).

With these changes, our users property is now non-null, and we can remove all the unnecessary !! operator occurrences.

val userNames: MutableList<String?> = ArrayList(users.size)
for ((firstName, lastName) in users) {
    ...
}

Also, since the users variable is already initialized, we have to remove the initialization from the init block:

init {
    val user1 = User("Jane", "")
    val user2 = User("John", null)
    val user3 = User("Anne", "Doe")

    users.add(user1)
    users.add(user2)
    users.add(user3)
}

Since both lastName and firstName can be null, we need to handle nullability when we build the list of formatted user names. Since we want to display "Unknown" if either name is missing, we can make the name non-null by removing ? from the type declaration.

val name: String

If the lastName is null, name is either the firstName or "Unknown":

if (lastName != null) {
    if (firstName != null) {
        name = "$firstName $lastName"
    } else {
        name = lastName
    }
} else if (firstName != null) {
    name = firstName
} else {
    name = "Unknown"
}

This can be written more idiomatically by using the elvis operator ?:. The elvis operator will return the expression on its left hand side if it's not null, or the expression on its right hand side, if the left hand side is null.

So in the following code user.firstName is returned if it is not null. If user.firstName is null, the expression returns the value on the right hand , "Unknown":

if (lastName != null) {
    ...
} else {
    name = firstName ?: "Unknown"
}

Kotlin makes working with Strings easy with String templates. String templates allow you to reference variables inside string declarations.

The automatic converter updated the concatenation of the first and last name to reference the variable name directly in the string by using the $ symbol and put the expression between { } .

// Java
name = user.getFirstName() + " " + user.getLastName();

// Kotlin
name = "${user.firstName} ${user.lastName}"

In code, replace the String concatenation with:

name = "$firstName $lastName"

In Kotlin if, when, for, and while are expressions—they return a value. Your IDE is even showing a warning that the assignment should be lifted out of the if:

Let's follow the IDE's suggestion and lift the assignment out for both if statements. The last line of the if statement will be assigned. Like this, it's clearer that this block's only purpose is to initialise the name value:

name = if (lastName != null) {
    if (firstName != null) {
        "$firstName $lastName"
    } else {
        lastName
    }
} else {
   firstName ?: "Unknown"
}

Next, we'll get a warning that the name declaration can be joined with the assignment. Let's apply this as well. Because the type of the name variable can be deduced, we can remove the explicit type declaration. Now our formattedUserNames looks like this:

val formattedUserNames: List<String?>
   get() {
       val userNames: MutableList<String?> = ArrayList(users.size)
       for ((firstName, lastName) in users) {
           val name = if (lastName != null) {
               if (firstName != null) {
                   "$firstName $lastName"
               } else {
                   lastName
               }
           } else {
               firstName ?: "Unknown"
           }
           userNames.add(name)
       }
       return userNames
   }

Let's take a closer look at the formattedUserNames getter and see how we can make it more idiomatic. Right now the code does the following:

  • Creates a new list of strings
  • Iterates through the list of users
  • Constructs the formatted name for each user, based on the user's first and last name
  • Returns the newly created list
val formattedUserNames: List<String>
        get() {
            val userNames = ArrayList<String>(users.size)
            for ((firstName, lastName) in users) {
                val name = if (lastName != null) {
                    if (firstName != null) {
                        "$firstName $lastName"
                    } else {
                        lastName
                    }
                } else {
                    firstName ?: "Unknown"
                }

                userNames.add(name)
            }
            return userNames
        }

Kotlin provides an extensive list of collection transformations that make development faster and safer by expanding the capabilities of the Java Collections API. One of them is the map function. This function returns a new list containing the results of applying the given transform function to each element in the original list. So, instead of creating a new list and iterating through the list of users manually, we can use the map function and move the logic we had in the for loop inside the map body. By default, the name of the current list item used in map is it, but for readability you can replace it with your own variable name. In our case, let's name it user:

    
val formattedUserNames: List<String>
        get() {
            return users.map { user ->
                val name = if (user.lastName != null) {
                    if (user.firstName != null) {
                        "${user.firstName} ${user.lastName}"
                    } else {
                        user.lastName ?: "Unknown"
                    }
                }  else {
                    user.firstName ?: "Unknown"
                }
                name
            }
        }

To simplify this even more, we can remove the name variable completely:

    
val formattedUserNames: List<String>
        get() {
            return users.map { user ->
                if (user.lastName != null) {
                    if (user.firstName != null) {
                        "${user.firstName} ${user.lastName}"
                    } else {
                        user.lastName ?: "Unknown"
                    }
                }  else {
                    user.firstName ?: "Unknown"
                }
            }
        }

We saw that the automatic converter replaced the getFormattedUserNames() function with a property called formattedUserNames that has a custom getter. Under the hood, Kotlin still generates a getFormattedUserNames() method that returns a List.

In Java, we would expose our class properties via getter and setter functions. Kotlin allows us to have a better differentiation between properties of a class, expressed with fields, and functionalities, actions that a class can do, expressed with functions. In our case, the Repository class is very simple and doesn't do any actions so it only has fields.

The logic that was triggered in the Java getFormattedUserNames() function is now triggered when calling the getter of the formattedUserNames Kotlin property.

While we don't explicitly have a field corresponding to the formattedUserNames property, Kotlin does provide us an automatic backing field named field which we can access if needed from custom getters and setters.

Sometimes, however, we want some extra functionality that the automatic backing field doesn't provide. Let's go through an example below.

Inside our Repository class, we have a mutable list of users which is being exposed in the function getUsers() which was generated from our Java code:

fun getUsers(): List<User>? {
    return users
}

The problem here is that by returning users any consumer of the Repository class can modify our list of users - not a good idea! Let's fix this by using a backing property.

First, let's rename users to _users. Now add a public immutable property that returns a list of users. Let's call it users:

private val _users = mutableListOf<User>()
val users: List<User>
      get() = _users

With this change, the private _users property becomes the backing property for the public users property. Outside of the Repository class, the _users list is not modifiable, as consumers of the class can access the list only through users.

Full code:

object Repository {

    private val _users = mutableListOf<User>()
    val users: List<User>
        get() = _users

    val formattedUserNames: List<String>
        get() {
            return _users.map { user ->
                if (user.lastName != null) {
                    if (user.firstName != null) {
                        "${user.firstName} ${user.lastName}"
                    } else {
                        user.lastName ?: "Unknown"
                    }
                }  else {
                    user.firstName ?: "Unknown"
                }
            }
        }

    init {

        val user1 = User("Jane", "")
        val user2 = User("John", null)
        val user3 = User("Anne", "Doe")

        _users.add(user1)
        _users.add(user2)
        _users.add(user3)
    }
}

Right now the Repository class knows how to compute the formatted user name for a User object. But if we want to reuse the same formatting logic in other classes, we need to either copy and paste it or move it to the User class.

Kotlin provides the ability to declare functions and properties outside of any class, object, or interface. For example, the mutableListOf() function we used to create a new instance of a List is defined directly in Collections.kt from the Standard Library.

In Java, whenever you need some utility functionality, you would most likely create a Util class and declare that functionality as a static function. In Kotlin you can declare top-level functions, without having a class. However, Kotlin also provides the ability to create extension functions. These are functions that extend a certain type but are declared outside of the type. As such, they have an affinity to that type.

The visibility of extension functions and properties can be restricted by using visibility modifiers. These restrict the usage only to classes that need the extensions, and don't pollute the namespace.

For the User class, we can either add an extension function that computes the formatted name, or we can hold the formatted name in an extension property. It can be added outside the Repository class, in the same file:

// extension function
fun User.getFormattedName(): String {
    return if (lastName != null) {
        if (firstName != null) {
            "$firstName $lastName"
        } else {
            lastName ?: "Unknown"
        }
    } else {
        firstName ?: "Unknown"
    }
}

// extension property
val User.userFormattedName: String
    get() {
        return if (lastName != null) {
            if (firstName != null) {
                "$firstName $lastName"
            } else {
                lastName ?: "Unknown"
            }
        } else {
            firstName ?: "Unknown"
        }
    }

// usage:
val user = User(...)
val name = user.getFormattedName()
val formattedName = user.userFormattedName

We can then use the extension functions and properties as if they're part of the User class.

Because the formatted name is a property of the user and not a functionality of the Repository class, let's use the extension property. Our Repository file now looks like this:

val User.formattedName: String
    get() {
        return if (lastName != null) {
            if (firstName != null) {
                "$firstName $lastName"
            } else {
                lastName ?: "Unknown"
            }
        } else {
            firstName ?: "Unknown"
        }
    }

object Repository {

    private val _users = mutableListOf<User>()
    val users: List<User>
      get() = _users

    val formattedUserNames: List<String>
        get() {
            return _users.map { user -> user.formattedName }
        }

    init {

        val user1 = User("Jane", "")
        val user2 = User("John", null)
        val user3 = User("Anne", "Doe")

        _users.add(user1)
        _users.add(user2)
        _users.add(user3)
    }
}

The Kotlin Standard Library uses extension functions to extend the functionality of several Java APIs; a lot of the functionalities on Iterable and Collection are implemented as extension functions. For example, the map function we used in a previous step is an extension function on Iterable.

In our Repository class code, we are adding several user objects to the _users list. These calls can be made more idiomatic with the help of scope functions.

To execute code only in the context of a specific object, without needing to access the object based on its name, Kotlin created 5 scope functions: let, apply, with, run and also. Short and powerful, all of these functions have a receiver (this), may have an argument (it) and may return a value. You would decide which one to use depending on what you want to achieve.

Here's a handy cheat sheet to help you remember this:

Since we're configuring our _users object in our Repository, we can make the code more idiomatic by using the apply function:

init {
    val user1 = User("Jane", "")
    val user2 = User("John", null)
    val user3 = User("Anne", "Doe")
   
    _users.apply {
       // this == _users
       add(user1)
       add(user2)
       add(user3)
    }
 }

In this codelab, we covered the basics you need to start refactoring your code from Java to Kotlin. This refactoring is independent of your development platform and helps to ensure that the code you write is idiomatic.

Idiomatic Kotlin makes writing code short and sweet. With all the features Kotlin provides, there are so many ways to make your code safer, more concise, and more readable. For example, we can even optimize our Repository class by instantiating the _users list with users directly in the declaration, getting rid of the init block:

private val users = mutableListOf(User("Jane", ""), User("John", null), User("Anne", "Doe"))

We covered a large array of topics, from handling nullability, singletons, Strings, and collections to topics like extension functions, top-level functions, properties, and scope functions. We went from two Java classes to two Kotlin ones that now look like this:

User.kt

class User(var firstName: String?, var lastName: String?)

Repository.kt

val User.formattedName: String
    get() {
       return if (lastName != null) {
            if (firstName != null) {
                "$firstName $lastName"
            } else {
                lastName ?: "Unknown"
            }
        } else {
            firstName ?: "Unknown"
        }
    }

object Repository {

    private val _users = mutableListOf(User("Jane", ""), User("John", null), User("Anne", "Doe"))
    val users: List<User>
        get() = _users

    val formattedUserNames: List<String>
        get() = _users.map { user -> user.formattedName }
}

Here's a TL;DR of the Java functionalities and their mapping to Kotlin:

Java

Kotlin

final object

val object

equals()

==

==

===

Class that just holds data

data class

Initialization in the constructor

Initialization in the init block

static fields and functions

fields and functions declared in a companion object

Singleton class

object

To find out more about Kotlin and how to use it on your platform, check out these resources: