Java To Kotlin Part 5 — The Five Siblings

Michael Duivestein
Tech@Travelstart
Published in
3 min readMar 4, 2019

This is part 5 in a series of posts intended to help a Java developer convert to Kotlin. Should you be new to this series, you can find part 1 here.

The five siblings are apply, also, let, run, and with. They act in much the same manner but have a few, sometimes subtle, differences.

This post is going to rely heavily on an understanding of receivers. If you’re unfamiliar with receivers, or just want a refresher, please refer to the following:

  • This is a general article on receivers in object oriented programming.
  • This contains a particularly insightful StackOverflow answer on receivers in Kotlin.

with, apply, and run act on the receiver inside a block of code by turning the receiver into this. This means that they are very handy for running a set of commands on a single object. let’s use this EmployeeBuilder as an example:

If we wanted to use EmployeeBuilder to create an Employee, we could do the following:

The setting of parameters in the EmployeeBuilder could be done with less boilerplate by making the EmployeeBuilder the receiver. The following will demonstrate some of the different ways we could do this:

With

The with method is pretty simple. It allows us to use employeeBuilder as a receiver:

The with statement is a transformer function. This means that it returns whatever the lambda returns. This means we can call buildEmployee() inside the with block and the following result will be returned:

This is great because we can just in-line the whole function:

Apply

apply works in almost the same way as with. There are two noticeable differences:
The first difference is that apply is called on the object you want as the receiver, as opposed to with taking the receiver as a parameter:

The second difference is that, instead of returning the result like in with, apply returns the object that it was was applied to. This also means that you can’t return Employee from inside the lambda, as it expects an EmployeeBuilder. This is handy, as we can just append the buildEmployee() method onto the end of the apply block:

apply is useful for configuring an object outside of it’s constructor. It’s also useful for general method chaining, especially at any place where you can ignore the return of a method call. For example, apply could be used for setting the attributes of a Calendar class, as it has already been declared and we don’t need to act on anything that results from the setting of the attributes.

When viewing the decompiled Kotlin code, both the standard method, apply, and with generate the same code:

This is notable because it means these are purely convenience functions. They generate no additional overhead during runtime, so don’t be shy about using them.

Run

run can be thought of as a middle ground between with and apply. We call run in the same way as apply, by chaining it to the object we want to be the receiver. Like with, run can return an object inside of the lambda:

Let

let (and also) binds to the parameter passed into it instead of the receiver. This means that instead of just calling a function belonging to the receiver, we need to call it.[function]():

As can be seen in this sample, let (like with and run) returns the contents of the lambda.

Also

Our final sibling is also. As mentioned earlier, also acts on the parameter instead of the receiver. The difference between let and also is that, like apply, it returns the same object that the lambda was called on:

Examples for Part 5 can be found here.

Further reading:

Here is an in-depth article on these methods. Note that in many places these are called the “standard functions”. This is partly true. While the 5 siblings are standard functions, the Kotlin language contains many more standard functions.

Here is a handy spreadsheet that summarizes the functions that were covered in this post.

Originally published at k0ma.co.za on March 4, 2019.

--

--