difference between kotlin also, apply, let, use, takeIf and takeUnless in Kotlin

let, also, apply, takeIf, takeUnless are extension functions in Kotlin.

To understand these function you have to understand Extension functions and Lambda functions in Kotlin.

Extension Function:

By the use of extension function, we can create a function for a class without inheriting a class.

Kotlin, similar to C# and Gosu, provides the ability to extend a class with new functionality without having to inherit from the class or use any type of design pattern such as Decorator. This is done via special declarations called extensions. Kotlin supports extension functions and extension properties.

So, to find if only numbers in the String, you can create a method like below without inheriting String class.

fun String.isNumber(): Boolean = this.matches("[0-9]+".toRegex())

you can use the above extension function like this,

val phoneNumber = "8899665544"
println(phoneNumber.isNumber())

which is prints true.

Lambda Functions:

Lambda functions are just like Interface (contains only one method. Also called as Single Abstract Method) in Java. From Java 8 lambdas are available in java also. In Kotlin lambdas used everywhere. Mostly lambdas passed as a parameter in a function.

Example:

fun String.isNumber(block: () -> Unit): Boolean {
    return if (this.matches("[0-9]+".toRegex())) {
        block()
        true
    } else false
}

You can see, the block is a lambda function and it is passed as a parameter. You can use the above function like this,

val phoneNumber = "8899665544"
phoneNumber.isNumber {
   println("Block executed")
}

The above function will print like this,

Block executed

I hope, now you got an idea about Extension functions and Lambda functions. Now we can go to Extension functions one by one.

let

public inline fun <T, R> T.let(block: (T) -> R): R = block(this)

Two Types T and R used in the above function.

T.let

T could be any object like String, number, or any type. So you can invoke this function with any objects.

block: (T) -> R

You can see the lambda function In parameter of let the invoking object is passed as a parameter of the function. So you can use the invoking class object inside the function. then it returns the R (another object).

Example:

val phoneNumber = "8899665544"
val numberAndCount: Pair<Int, Int> = phoneNumber.let { it.toInt() to it.count() }

In above example let takes String as a parameter of its lambda function and it returns Pair in return.

In the same way, other extension functions works.

also

public inline fun <T> T.also(block: (T) -> Unit): T { block(this); return this }

extension function also takes the invoking class as a lambda function parameter and returns nothing.

Example:

val phoneNumber = "8899665544"
phoneNumber.also { number ->
    println(number.contains("8"))
    println(number.length)
 }

apply

public inline fun <T> T.apply(block: T.() -> Unit): T { block(); return this }

Same as also but the same invoking object passed as the function so you can use the functions and other properties without calling it or parameter name.

Example:

val phoneNumber = "8899665544"
phoneNumber.apply { 
    println(contains("8"))
    println(length)
 }

You can see in the above example the functions of String class directly invoked inside the lambda funtion.

takeIf

public inline fun <T> T.takeIf(predicate: (T) -> Boolean): T? = if (predicate(this)) this else null

Example:

val phoneNumber = "8899665544"
val number = phoneNumber.takeIf { it.matches("[0-9]+".toRegex()) }

In above example number will have a string of phoneNumber only it matches the regex. Otherwise, it will be null.

takeUnless

public inline fun <T> T.takeUnless(predicate: (T) -> Boolean): T? = if (!predicate(this)) this else null

It is the reverse of takeIf.

Example:

val phoneNumber = "8899665544"
val number = phoneNumber.takeUnless { it.matches("[0-9]+".toRegex()) }

number will have a string of phoneNumber only if not matches the regex. Otherwise, it will be null.


let

public inline fun <T, R> T.let(block: (T) -> R): R = block(this)

Take the receiver and pass it to a function passed as a parameter. Return the result of the function.

val myVar = "hello!"
myVar.let { println(it) } // Output "hello!"

You can use let for null safety check:

val myVar = if (Random().nextBoolean()) "hello!" else null
myVar?.let { println(it) } // Output "hello!" only if myVar is not null

also

public inline fun <T> T.also(block: (T) -> Unit): T { block(this); return this }

Execute the function passed with the receiver as parameter and return the receiver.
It's like let but always return the receiver, not the result of the function.

You can use it for doing something on an object.

val person = Person().also {
  println("Person ${it.name} initialized!")
  // Do what you want here...
}

takeIf

public inline fun <T> T.takeIf(predicate: (T) -> Boolean): T? = if (predicate(this)) this else null

Return the receiver if the function (predicate) return true, else return null.

println(myVar.takeIf { it is Person } ?: "Not a person!")

takeUnless

public inline fun <T> T.takeUnless(predicate: (T) -> Boolean): T? = if (!predicate(this)) this else null

Same as takeIf, but with predicate reversed. If true, return null, else return the receiver.

println(myVar.takeUnless { it is Person } ?: "It's a person!")

Help

  • You can use https://try.kotlinlang.org/ for testing easily. You can find examples here.
  • You can checkout the source of the standard lib here. let, also, takeIf and takeUnless here.