1. Welcome!
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
, andrun
keywords
Assumptions
You should already be familiar with Java.
What you'll need
2. Getting set up
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 the No Activity template. Choose Kotlin as the project language. Minimum SDK can be of any value, it will not affect the outcome.
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;
}
}
You'll notice your IDE is telling you @Nullable
is not defined. So import androidx.annotation.Nullable
if you use Android Studio, or org.jetbrains.annotations.Nullable
if you're using IntelliJ.
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;
}
}
3. Declaring nullability, val, var and data classes
Our IDE can do a pretty good job of automatically converting Java code into Kotlin code but sometimes it needs a little help. Let's let our IDE do an initial pass at the conversion. Then we'll go through the resulting code to understand how and why it has been converted 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 correction 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 read-only 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 conversion 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 callsequals()
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
4. Default arguments, named arguments
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("Joe", "Doe")
Kotlin allows you to label your arguments when your functions are called:
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 must 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")
Default values are an important and often used concept in Kotlin code. In our codelab we want to always specify the first and last name in a User
object declaration, so we don't need default values.
5. Object initialization, companion object and singletons
Before continuing the codelab, make sure that your User
class is a data
class. Now, 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:
- The list of
users
is nullable since the object wasn't instantiated at declaration time - Functions in Kotlin like
getUsers()
are declared with thefun
modifier - The
getFormattedUserNames()
method is now a property calledformattedUserNames
- The iteration over the list of users (that was initially part of
getFormattedUserNames(
) ) has a different syntax than the Java one - The
static
field is now part of acompanion object
block - An
init
block was added
Before we go further, let's clean up the code a bit. If we look in the constructor, we notice the converter made our users
list a mutable list that holds nullable objects. While the list can indeed be null, let's assume it can't hold null users. So let's do the following:
- Remove the
?
inUser?
within theusers
type declaration - Remove the
?
inUser?
for the return type ofgetUsers()
so it returnsList<User>?
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 the companion object block 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 replace the class definition with object Repository
. Remove the companion object as well.
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 formattedUserNames = Repository.formattedUserNames
Note that if a property does not have a visibility modifier on it, it is public by default, as in the case of formattedUserNames
property in the Repository
object.
6. Handling nullability
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. As a result, for all the usages of the users
object, the not-null assertion operator !!
needs to be used. (You'll see users!!
and user!!
throughout the converted code.) The !!
operator converts any variable to a non-null type, so you can access properties or call functions on it. However, an exception will be thrown if the variable value is indeed 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 (in the init
block). Thus we can directly instantiate the users
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 and provide the list element type. mutableListOf<User>()
creates an empty list that can hold User
objects. Since the data type of the variable can now be inferred by the compiler, remove the explicit type declaration of the users
property.
private val users = mutableListOf<User>()
We also changed var
into val
because users will contain a read-only reference to the list of users. Note that the reference is read-only, so it can never point to a new list, but the list itself is still mutable (you can add or remove elements).
Since the users
variable is already initialized, remove this initialization from the init
block:
users = ArrayList<Any?>()
Then the init
block should look like this:
init {
val user1 = User("Jane", "")
val user2 = User("John", null)
val user3 = User("Anne", "Doe")
users.add(user1)
users.add(user2)
users.add(user3)
}
With these changes, our users
property is now non-null, and we can remove all the unnecessary !!
operator occurrences. Note that you will still see compile errors in Android Studio, but continue with the next few steps of the codelabs to resolve them.
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)
}
Also, for the userNames
value, if you specify the type of ArrayList
as holding Strings
, then you can remove the explicit type in the declaration because it will be inferred.
val userNames = ArrayList<String>(users.size)
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. Update the for
loop as shown below. Replace all instances of user.firstName
with firstName
and replace user.lastName
with lastName
.
for ((firstName, lastName) in users) {
var name: String
name = if (lastName != null) {
if (firstName != null) {
firstName + " " + lastName
} else {
lastName
}
} else if (firstName != null) {
firstName
} else {
"Unknown"
}
userNames.add(name)
}
if expression
The names in the list of userNames are not quite in the format that we want yet. Since both lastName
and firstName
can be null
, we need to handle nullability when we build the list of formatted user names. We want to display "Unknown"
if either name is missing. Since the name
variable will not be changed after it's set once, we can use val
instead of var
. Make this change first.
val name: String
Take a look at the code that sets the name variable. It may look new to you to see a variable being set to equal an if
/ else
block of code. This is allowed because in Kotlin if
and when
are expressions—they return a value. The last line of the if
statement will be assigned to name
. This block's only purpose is to initialize the name
value.
Essentially, this logic presented here is if the lastName
is null, name
is either set to the firstName
or "Unknown"
.
name = if (lastName != null) {
if (firstName != null) {
firstName + " " + lastName
} else {
lastName
}
} else if (firstName != null) {
firstName
} else {
"Unknown"
}
Elvis operator
This code 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, firstName
is returned if it is not null. If firstName
is null, the expression returns the value on the right hand , "Unknown"
:
name = if (lastName != null) {
...
} else {
firstName ?: "Unknown"
}
7. String templates
Kotlin makes working with String
s easy with String templates. String templates allow you to reference variables inside string declarations by using the $ symbol before the variable. You could also put an expression within a string declaration, by placing the expression within { } and using the $ symbol before it. Example: ${user.firstName}
.
Your code currently uses string concatenation to combine the firstName
and lastName
into the user name.
if (firstName != null) {
firstName + " " + lastName
}
Instead, replace the String concatenation with:
if (firstName != null) {
"$firstName $lastName"
}
Using string templates can simplify your code.
Your IDE will show you warnings if there is a more idiomatic way to write your code. You'll notice a squiggly underline in the code, and when you hover over it, you'll see a suggestion for how to refactor your code.
Currently, you should see a warning that the name
declaration can be joined with the assignment. Let's apply this. Because the type of the name
variable can be deduced, we can remove the explicit String
type declaration. Now our formattedUserNames
looks like this:
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
}
We can make one additional tweak. Our UI logic displays "Unknown"
in case the first and last names are missing, so we're not supporting null objects. Thus, for the data type of formattedUserNames
replace List<String?>
with List<String>
.
val formattedUserNames: List<String>
8. Operations on collections
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
}
}
Notice that we use the Elvis operator to return "Unknown"
if user.lastName
is null, since user.lastName
is of type String?
and a String
is required for the name
.
...
else {
user.lastName ?: "Unknown"
}
...
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"
}
}
}
9. Properties and backing properties
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.
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
}
Because we didn't want the callers of the Repository
class to modify the users list, we created the getUsers()
function that returns a read-only List<User>
. With Kotlin, we prefer using properties rather than functions for such cases. More precisely, we would expose a read-only List<User>
that is backed by a mutableListOf<User>
.
First, let's rename users
to _users
. Highlight the variable name, right click to Refactor > Rename the variable. Then add a public read-only property that returns a list of users. Let's call it users
:
private val _users = mutableListOf<User>()
val users: List<User>
get() = _users
At this point, you can delete the getUsers()
method.
With the above 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
.
When users
is called from Kotlin code, the List
implementation from the Kotlin Standard Library is used, where the list is not modifiable. If users
is called from Java, the java.util.List
implementation is used, where the list is modifiable and operations like add() and remove() are available.
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)
}
}
10. Top-level and extension functions and properties
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 already defined in Collections.kt
from the Kotlin 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.
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
class 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
.
11. Scope functions: let, apply, with, run, also
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 Kotlin scope functions.
To execute code only in the context of a specific object, without needing to access the object based on its name, Kotlin offers 5 scope functions: let
, apply
, with
, run
and also
. These functions make your code easier to read and more concise. All scope functions have a receiver (this
), may have an argument (it
) and may return a value.
Here's a handy cheat sheet to help you remember when to use each function:
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)
}
}
12. Wrap up
In this codelab, we covered the basics you need to start converting your code from Java to Kotlin. This conversion is independent of your development platform and helps to ensure that the code you write is idiomatic Kotlin.
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
data 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 |
|
|
|
|
|
|
Class that just holds data |
|
Initialization in the constructor | Initialization in the |
| fields and functions declared in a |
Singleton class |
|
To find out more about Kotlin and how to use it on your platform, check out these resources:
- Kotlin Koans
- Kotlin Tutorials
- Android Kotlin Fundamentals
- Kotlin Bootcamp for Programmers
- Kotlin for Java developers - free course in Audit mode