Lambdas Expressions
As a first-class construct, functions are also data types, so you can store functions in variables, pass them to functions, and return them from functions.
Lambdas Expressions are essentially anonymous functions that we can treat as values — we can, for example, pass them as arguments to functions, return them, or do any other thing we could do with a normal object.
Lambda expression syntax
The full syntactic form of lambda expressions is as follows:
val sum: (Int, Int) -> Int
= { x: Int, y: Int -> x + y }
That syntax includes the following rules:
- A lambda expression is always surrounded by curly braces.
- Parameter declarations in the full syntactic form go inside curly braces and may have optional type annotations.
- Parameter declarations and the lambda body must be separated
by a->
and the body goes after the->
. - If the inferred return type of the lambda is not
Unit
, the last expression inside the lambda body is treated as the return value. - The return value may be inferred from the lambda body
- If there is a single parameter, it may be accessed within the lambda body using an implicit
it
reference.
If you leave all the optional annotations out, what’s left looks like this:
val sum = { x: Int, y: Int -> x + y }
Let’s look at some code that illustrates this lambda expression syntax.
Example 1: a function with no parameter and return nothing:-
val lambda1 : () -> Unit
= { println("Hello") }
In this case, lambda1
is a function that takes no arguments and returns Unit
. Because there are no argument types to declare, and the return value may be inferred from the lambda body, we may simplify this lambda even further (as below).
val lambda2 = { println("Hello") }
The Unit
return is inferred by the fact that the last expression of the lambda body, the call to println()
, returns Unit
.
val lambda1 : () -> Unit = { println("Hello") }
// Case 1: full syntactic form
val lambda2 = { println("Hello") }
// Case 2: declare type of parameter on right side,
// but we have no parameter in this case
fun main()
{
lambda1()
lambda2()
}
Output:
Hello
Hello
Example 2: a function with one parameter and return nothing:-
val lambda3 : (String) -> Unit
= { name: String -> println("Hello $name") }
It includes all optional type information. The name parameters include their explicit type information. The variable (lambda3) also explicitly defines the type information for the function expressed by the lambda.
We may simplify this lambda even further (as below).
val lambda4 = { name: String -> println("Hello $name") }
val lambda5 : (String) -> Unit
= { name -> println("Hello $name") }
In the lambda4
example, the type information is inferred from the lambda itself. The parameter values are explicitly annotated with the String
type while the final expression can be inferred to return Unit
.
For lambda5
, the variable (lambda5
) includes the type information. Because of this, the parameter declarations of the lambda can omit the explicit type annotations; name
will be inferred as String
types.
val lambda3 : (String) -> Unit
= { name: String -> println("Hello $name") }
// case 1: full syntactic form
val lambda4 = { name: String -> println("Hello $name") }
// case 2: declare type of parameter on right side
val lambda5 : (String) -> Unit
= { name -> println("Hello $name") }
// case 3: declare type of parameter on left side
val lambda6 : (String) -> Unit = { println("Hello $it") }
// case 4: Uses of it if we have single parameter
fun main()
{
lambda3("Gaurav Tyagi")
lambda4("Gaurav Tyagi")
lambda5("Gaurav Tyagi")
lambda6("Gaurav Tyagi")
}
Output:
Hello Gaurav Tyagi
Hello Gaurav Tyagi
Hello Gaurav Tyagi
Hello Gaurav Tyagi
Example 3: a function with two parameters and return some value:-
val lambda7 : (String, String) -> String
= { first: String, last: String ->
"My name is $first $last"
}
It includes all optional type information. Both the first and last parameters include their explicit type information. The variable (lambda7) also explicitly defines the type information for the function expressed by the lambda.
We may simplify this lambda even further (as below).
val lambda8 = { first: String, last: String ->
"My name is $first $last"
}
val lambda9 : (String, String) -> String = { first, last ->
var fullName = first+" "+last
"My name is $fullName"
}
In the lambda8
example, the type information is inferred from the lambda itself. The parameter values are explicitly annotated with the String
type while the final expression can be inferred to return aString
.
For lambda9
, the variable (lambda9) includes the type information. Because of this, the parameter declarations of the lambda can omit the explicit type annotations; first
and last
will both be inferred as String
types.
val lambda7 : (String, String) -> String
= { first: String, last: String ->
"My name is $first $last"
}
// case 1: full syntactic form
val lambda8 = { first: String, last: String ->
"My name is $first $last"
}
// case 2: declare type of parameter on right side
val lambda9 : (String, String) -> String = { first , last ->
var fullName = first+" "+last
"My name is $fullName"
}
// case 3: declare type of parameter on left side
fun main()
{
var name1 = lambda7("Gaurav", "Tyagi")
println(name1)
var name2 =lambda8("Gaurav", "Tyagi")
println(name2)
var name3 =lambda9("Gaurav", "Tyagi")
println(name3)
}
Output:
My name is Gaurav Tyagi
My name is Gaurav Tyagi
My name is Gaurav Tyagi
Functional (SAM) interfaces
An interface with only one abstract method is called a functional interface, or a Single Abstract Method (SAM) interface. The functional interface can have several non-abstract members but only one abstract member.
To declare a functional interface in Kotlin, use the fun
modifier.
fun interface KRunnable {
fun invoke()
}
Traditional way:
interface Greeterlistener {
fun greet(item: String)
}
fun greetMe(name: String, greeter: Greeterlistener) {
greeter.greet(name)
}
fun main() {
// Creating an instance of a class
greetMe("Gaurav", object : Greeterlistener {
override fun greet(item: String) {
println("Hello $item")
}
})
}
Output:
Hello Gaurav
The greetMe
function takes an instance of a GreeterListener
interface. To satisfy the need, we create an anonymous class to implement Greeter
and define our greet
behavior.
With SAM conversion, we can simplify this.
fun interface Greeterlistener {
fun greet(item: String)
}
fun greetMe(name: String, greeter: Greeterlistener) {
greeter.greet(name)
}
fun main() {
greetMe("Gaurav", {
println("Hello $it")
})
greetMe("Gaurav") {
println("Hello $it")
}
}
Output:
Hello Gaurav
Hello Gaurav
Notice that the lambda here is now performing SAM conversion to represent the GreeterListener
type.
Notice also the change to the GreeterListener
interface. We added the fun
keyword to the interface. This marks the interface as a functional interface that will give a compiler error if you try to add more than one public abstract method.
If you’re creating an interface with a single public, abstract method, consider making it a functional interface so you may leverage lambdas when working with the type.
another example of SAM with returning String:
fun interface Greeterlistener {
fun greet(item: String): String
}
fun greetMe(name: String, greeter: Greeterlistener) : Int {
var res = greeter.greet(name)
return res.length
}
fun main() {
var result = greetMe("Gaurav", {
println("Hello $it")
"Hi $it"
})
println(result)
result = greetMe("Gaurav") {
println("Hello $it")
"Hi $it"
}
println(result)
}
Output:
Hello Gaurav
9
Hello Gaurav
9
another example to understand SAM:
consider the following Kotlin functional interface:
fun interface IntPredicate {
fun accept(i: Int): Boolean
}
If you don’t use a SAM conversion, you will need to write code like this:
// Creating an instance of a class
val isEven : IntPredicate = object : IntPredicate {
override fun accept(i: Int): Boolean {
return i % 2 == 0
}
}
By leveraging Kotlin’s SAM conversion, you can write the following equivalent code instead:
// Creating an instance using lambda
val isEven : IntPredicate = IntPredicate { it % 2 == 0 }
A short lambda expression replaces all the unnecessary code.
fun interface IntPredicate {
fun accept(i: Int): Boolean
}
val isEven : IntPredicate = IntPredicate { it % 2 == 0 }
fun main() {
println("Is 7 even? - ${isEven.accept(7)}")
}
Output:
Is 7 even? - false
Type aliases
You can also simply rewrite the above using a type alias for a functional type:
typealias IntPredicate = (i: Int) -> Boolean
val isEven: IntPredicate = { it % 2 == 0 }
fun main() {
println("Is 7 even? - ${isEven(7)}")
}
Output:
Is 7 even? - false
Returning values from a lambda
In the previous section, We demonstrated that the return value of a lambda is provided by the last expression within the lambda body. This is true whether returning a meaningful value or when returning Unit
.
But what if you want to have multiple return statements within your lambda expression?
Implementation of multiple returns within a lambda expression.
val lambda = { greeting: String, name: String ->
if(greeting.length < 3) return // error: return not allowed here
println("$greeting $name")
}
with lambda expressions, adding a return
in this way results in a compiler error.
To accomplish the desired result, we must use qualified return (as below).
val lambda = greet@ { greeting: String, name: String ->
if(greeting.length < 3) return@greet
println("$greeting $name")
}
here, we’ve labeled our lambda by adding greet@
before the first curly brace. Now we can reference this label and use it to return (return@greet) from our lambda to the outer, calling function.
The above example doesn’t return any meaningful value as return type for this case is Unit
. What if we wanted to return a String
rather than printing a String
?
val lambda = greet@ { greeting: String, name: String ->
if(greeting.length < 3) return@greet ""
if(greeting.length < 6) return@greet "Welcome!"
"$greeting $name"
}
Notice that while we now have multiple return
statements, we still do not use an explicit return
for our final value. This is important. If we added a return
to our final line of the lambda expression body, we would get a compiler error. The final return value must always be implicitly returned.
another example to return from lambda:
fun calculateSum1() {
val numbers = listOf(1, 2, 3, 4, 5)
var sum = 0
numbers.forEach { number ->
if (number == 3) {
return@forEach
// Exit the lambda and continue with the next iteration
}
sum += number
}
println("Sum: $sum")
}
fun calculateSum2() {
val numbers = listOf(1, 2, 3, 4, 5)
var sum = 0
numbers.forEach greet@{ number ->
if (number == 3) {
return@greet
// Exit the lambda and continue with the next iteration
}
sum += number
}
println("Sum: $sum")
}
fun main() {
calculateSum1()
calculateSum2()
}
Output:
Sum: 12
Sum: 12
forEach build-in function in kotlin is as:
public inline fun <T> Iterable<T>.forEach(action: (T) -> Unit): Unit {
for (element in this) action(element)
}
Ignore the parameters
If we want to ignore one, or more parameters, we must rely on the underscore naming convention.
val lambda10: (String, String) -> Unit = { _, _ ->
println("Hello there!")
}
val lambda11: (String, String) -> Unit = { _, last ->
println("Hello $last")
}
val lambda12: (String) -> Unit = { _ ->
println("Hello there!")
}
val lambda13: (String) -> Unit = {
println("Hello there!")
}
// lambda13 is because of it only
fun main() {
lambda10("Gaurav", "Tyagi")
lambda11("Gaurav", "Tyagi")
lambda12("Gaurav")
lambda13("Gaurav")
}
Output:
Hello there!
Hello Tyagi
Hello there!
Hello there!
Working with Build-In Functional Interfaces
Supplier: A Supplier is used when you want to generate or supply values without taking any input.
// Build-In
public interface Supplier<T> {
T get();
}
import java.util.function.*
private fun useOfSupplier(expr : Supplier<ArrayList<Int>>) {
var result: ArrayList<Int> = expr.get()
println(result) // Output: [1, 2, 3]
}
private fun testSupplier() {
var list : ArrayList<Int> = ArrayList<Int>()
list.add(1)
list.add(2)
list.add(3)
// return something but accept nothing
useOfSupplier { list }
}
fun main() {
testSupplier()
}
Consumer: A Consumer is used when you want to do something
with a parameter but not return anything.
// Build-In
public interface Consumer<T> {
void accept(T t);
}
import java.util.function.*
private fun useOfConsumer(expr : Consumer<String>) {
expr.accept("Gaurav")
}
private fun testConsumer() {
// accept something but return nothing
useOfConsumer {
println(it) // Output: Gaurav
}
}
fun main() {
testConsumer()
}
Predicate: Predicate is often used when filtering or matching. It accept a single parameter and return Boolean.
// Build-In
public interface Predicate<T> {
boolean test(T t);
}
import java.util.function.*
private fun useOfPredicate(list : ArrayList<Int>, expr : Predicate<Int>) : ArrayList<Int> {
val newList : ArrayList<Int> = ArrayList<Int> ()
for(i in 0 until list.size) {
if(expr.test(list[i])) {
newList.add(list[i])
}
}
return newList
}
private fun testPredicate() {
var list : ArrayList<Int> = ArrayList<Int>()
list.add(1)
list.add(2)
list.add(3)
list.add(4)
// accept something but return Boolean
var result = useOfPredicate(list) {
it % 2 == 0
}
println("$result") // Output: [2, 4]
// accept something but return Boolean
result = useOfPredicate(list) { item ->
item % 2 == 0
}
println("$result") // Output: [2, 4]
}
fun main() {
testPredicate()
}
another example to understand Predicate by using user defined Predicate:
here internal kotlin function “filter” uses the something same as Predicate
// User Defined
fun interface Predicate<String> {
fun accept(str: String): Boolean
}
fun queries(todoList: ArrayList<String>, expr : Predicate<String>) : ArrayList<String> {
var result : ArrayList<String> = ArrayList<String>()
for (i in 0..todoList.size-1) {
if(expr.accept(todoList[i])){
result.add(todoList[i])
}
}
return result
}
fun main() {
val names = ArrayList<String>()
names.add("Gaurav")
names.add("Garv")
names.add("GauravTyagi")
var evenName = queries(names,{ it.length % 2 == 0 })
println(evenName)
evenName = queries(names){ it.length % 2 == 0 }
println(evenName)
val numbers = listOf(1, 2, 3, 4, 5)
val evenNumbers = numbers.filter { it % 2 == 0 }
println(evenNumbers)
}
Output:
[Gaurav, Garv]
[Gaurav, Garv]
[2, 4]
BiFunction: BiFunction is a Build-In function which accept 2 arguments and produces a result.
// Build-In
public interface BiFunction<T, U, R> {
R apply(T t, U u);
}
import java.util.function.*
private fun useOfBiFunction(expr : BiFunction<Int,Char,String>) {
var result = expr.apply(2,'E')
println("$result") // Output: 2 , E as a String
}
private fun testBiFunction() {
useOfBiFunction { v1, v2 ->
"$v1 , $v2"
}
}
fun main() {
testBiFunction()
}
Thanks
Hi, this is a comment.
To get started with moderating, editing, and deleting comments, please visit the Comments screen in the dashboard.
Commenter avatars come from Gravatar.