Hello everyone and welcome to article number 9 in the series. Today we’re going to look at something that maybe should have come up after the syntax articles or so, but better late than never.
I guess I was using some of these without really knowing or paying attention to the real difference between them but last week someone asked me the difference and I was like hmm… so here we are.
Kotlin provides a set of standard functions we can use to ease our way with the language and some of the problems we face regularly while programming. Today we’re going to look into a group of functions that seem very similar in the way of using but actually have different behaviours and therefore different goals:
let | apply | run | also | with
All these functions work in a similar way in the sense they allow you to change the scope of the current function or variable, and at the same time apply multiple operations on that same variable all in the same place. So, the important thing is to look at the argument (it)
the receiver (this)
and the result of the function. Let’s assume we have a SomeObject
data class and the following all the examples inside the playground function:
data class SomeObject(
var someField: String = "some default value",
var otherField: Int = 55
)
class StandardFunctions {
fun playground() {
var someObject = SomeObject()
val letExample = someObject?.let { ... }
val applyExample = someObject.apply { ... }
val runExample = someObject.run { ... }
val alsoExample = someObject.also { ... }
val withExample = with(someObject) { ... }
}
fun someOtherFunction() {
print("random message")
}
}
And now let’s look into each one of the standard functions in detail and understand how they work.
I believe most of us use let
as a null checker to apply or not some operations on an object. It’s probably the most famous example so nothing new here. From the official doc:
/**
* Calls the specified function [block] with `this` value as its argument and returns its result.
*/
val letExample = someObject?.let {
print(it.someField)
this.someOtherFunction()
"some result"
}
In this case, by using let
we only print someField
if someObject
is not null, otherwise, nothing inside runs.
Argument (it): The argument in the case of let
refers to the object where we’re applying the function, in this case, someObject
Receiver (this): The receiver refers to the class where we’re using the function, allowing us to call someOtherFunction()
using this
keyword.
Result: the result of let
is whatever we want. By default, the return type is Unit
if we don’t specify a return statement, but in our case, we’re returning same result
String. Remember that in Kotlin the return
keyword is not required in certain situations, this is one of them.
We can compare using apply to using a builder pattern. It allows us to apply operations on an object and return that same object. From the official doc:
/**
* Calls the specified function [block] with `this` value as its receiver and returns `this` value.
*/
val applyExample = someObject.apply {
someField = "some field"
this.otherField = 99
"some result"
}
Argument (it): argument is not applicable when we use the apply
function.
Receiver (this): The receiver refers to the object where we’re using the function, allowing us to access otherField
using this
keyword.
Result: the result of apply
is always the object where we’re using the function, in this case, someObject
is the result. The line with "some result"
string has no effect at all when using apply
and Android Studio even warns that the expression is unused.
With run
we can do the same as with apply
but we can return anything we want as the result of the function rather than getting the calling object by default. From the official doc:
/**
* Calls the specified function [block] with `this` value as its receiver and returns its result.
*/
val runExample = someObject.run {
print(someField)
this.otherField = 99
"some result"
}
Argument (it): argument is not applicable when we use the run
function.
Receiver (this): The receiver refers to the object where we’re using the function, allowing us to access otherField
using this
keyword.
Result: the result of run
is whatever we want. Same as with let
function, the default return type is Unit
if we don’t specify a return statement, but in our case we’re returning same result
String.
We can use also
to apply some operation in an object and return that same object. Similar to apply
in a way but you need to use the argument to access the object, as the receiver in this case refers to the class and not the calling object. From the official doc:
/**
* Calls the specified function [block] with `this` value as its argument and returns `this` value.
*/
val alsoExample = someObject.also {
print(it.someField)
this.someOtherFunction()
"some result"
}
Argument (it): The argument in the case of also
refers to the object where we’re applying the function, in this case, someObject
Receiver (this): The receiver refers to the class where we’re using the function, allowing us to call someOtherFunction()
using this
keyword.
Result: the result of also
is always the object where we’re using the function, in this case someObject
is the result. The line with "some result"
string has no effect at all when using also
We can use with
if we want to call several different methods on an object for example as we don’t need to do it.method()
all the time, we can just call the methods directly as the receiver inside the block is the object itself. And we can also return whatever we want. From the official doc:
/**
* Calls the specified function [block] with `this` value as its receiver and returns `this` value.
*/
val withExample = with(someObject) {
print(someField)
this.otherField = 99
"some result"
}
Argument (it): argument is not applicable when we use the with
function.
Receiver (this): The receiver refers to the object where we’re using the function, allowing us to access otherField
using this
keyword.
Result: the result of with
is whatever we want. Same as with let
and run
functions, the default return type is Unit
if we don’t specify a return statement, but in our case we’re returning same result
String.
To summarise and to have everything in the same place, I believe a table it’s a nice and quick way to refer to, when we need to remember how these standard functions work.
And that’s it for today, I hope it’s now more clear how these standard functions work and when to use one or the other. If you enjoyed the article don’t forget to 👏 and please share your ideas and comments as usual. The code is available under the standardfunctions
package on the series project. See you at number 10 👋
Kotlin backend? Yes it’s possible ⇦ PREVIOUS
NEXT ⇨ Kotlin runtime checks — require and check