Kotlin functions are blocks of reusable code that perform specific tasks and can accept input parameters while returning output values. Unlike Java methods, Kotlin functions are first-class citizens, meaning they can be stored in variables, passed as arguments, and returned from other functions. The fun keyword is used to declare all Kotlin functions, making the syntax clean and consistent across different function types.
The fundamental Kotlin function syntax follows a simple pattern that’s both readable and concise:
fun functionName(parameter1: Type1, parameter2: Type2): ReturnType {
// Function body
return value
}
Every Kotlin function declaration begins with the fun keyword, followed by the function name, parameter list in parentheses, optional return type after a colon, and the function body enclosed in curly braces.
Kotlin function parameters are defined with explicit type declarations, making the code more readable and type-safe. Function return types in Kotlin can be explicitly declared or inferred by the compiler when using single-expression functions.
Kotlin functions support default parameter values, reducing the need for function overloading:
fun createUserProfile(name: String, age: Int = 18, isActive: Boolean = true): String {
return "User: $name, Age: $age, Active: $isActive"
}
In this example, the Kotlin function createUserProfile
has default parameters for age
and isActive
. You can call this function with just the name parameter: createUserProfile("Alice")
.
Named arguments make Kotlin function calls more readable and allow you to specify parameters in any order:
fun calculateDistance(x1: Double, y1: Double, x2: Double, y2: Double): Double {
return kotlin.math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1))
}
// Using named arguments
val distance = calculateDistance(x1 = 0.0, y1 = 0.0, x2 = 3.0, y2 = 4.0)
Kotlin functions can accept a variable number of arguments using the vararg modifier:
fun calculateSum(vararg numbers: Int): Int {
var sum = 0
for (number in numbers) {
sum += number
}
return sum
}
This vararg function can be called with any number of integer arguments: calculateSum(1, 2, 3, 4, 5)
.
Lambda expressions are anonymous functions that can be treated as values in Kotlin. They’re extensively used in functional programming and are essential for working with collections and higher-order functions.
Kotlin lambda expressions follow a specific syntax pattern:
val lambda: (ParameterType) -> ReturnType = { parameter ->
// Lambda body
result
}
The basic structure includes parameters before the arrow (->
) and the function body after it. The last expression in the lambda body becomes the return value.
When a lambda expression has only one parameter, you can use the implicit ‘it’ keyword:
val numbers = listOf(1, 2, 3, 4, 5)
val doubled = numbers.map { it * 2 }
This lambda function multiplies each number by 2 using the ‘it’ keyword to reference the single parameter.
Lambda expressions with multiple parameters require explicit parameter names:
val combineStrings: (String, String) -> String = { first, second ->
"$first-$second"
}
val result = combineStrings("Hello", "World") // Returns "Hello-World"
Higher-order functions are Kotlin functions that take other functions as parameters or return functions. They enable powerful functional programming patterns and code reusability.
Here’s how to create a higher-order function that accepts another function as a parameter:
fun processData(data: List<Int>, operation: (Int) -> Int): List<Int> {
return data.map(operation)
}
// Usage with lambda
val doubled = processData(listOf(1, 2, 3)) { it * 2 }
// Usage with function reference
fun square(x: Int) = x * x
val squared = processData(listOf(1, 2, 3), ::square)
This higher-order function demonstrates how function parameters can make code more flexible and reusable.
Kotlin function types describe the signature of functions, including parameter types and return type:
// Function type declaration
val mathOperation: (Int, Int) -> Int = { a, b -> a + b }
// Function reference
fun multiply(x: Int, y: Int): Int = x * y
val multiplyRef: (Int, Int) -> Int = ::multiply
Function references using the ::
operator provide a clean way to pass existing functions to higher-order functions.
Extension functions allow you to add new functionality to existing classes without modifying their source code or using inheritance. This is one of Kotlin’s most powerful features.
Extension functions are defined outside the class but can be called as if they were members:
fun String.isPalindrome(): Boolean {
val cleaned = this.lowercase().filter { it.isLetter() }
return cleaned == cleaned.reversed()
}
// Usage
val text = "A man a plan a canal Panama"
println(text.isPalindrome()) // true
This extension function adds a palindrome check to the String class, demonstrating how to extend built-in types.
Extension functions can work with generic types to provide flexible functionality:
fun <T> List<T>.secondOrNull(): T? {
return if (this.size >= 2) this[1] else null
}
// Usage
val numbers = listOf(1, 2, 3, 4)
val names = listOf("Alice", "Bob", "Charlie")
println(numbers.secondOrNull()) // 2
println(names.secondOrNull()) // "Bob"
Inline functions tell the compiler to insert the function’s code directly at the call site, eliminating the overhead of function calls, especially useful with lambda parameters.
inline fun measureTime(block: () -> Unit): Long {
val startTime = System.currentTimeMillis()
block()
return System.currentTimeMillis() - startTime
}
// Usage
val executionTime = measureTime {
// Some time-consuming operation
Thread.sleep(100)
}
The inline function measureTime
accepts a lambda parameter and measures its execution time without the performance overhead of function calls.
Inline functions with multiple lambda parameters can use noinline and crossinline modifiers:
inline fun processWithCallbacks(
data: String,
noinline onError: (String) -> Unit,
crossinline onSuccess: () -> Unit
) {
if (data.isNotEmpty()) {
onSuccess()
} else {
onError("Data is empty")
}
}
Infix functions allow you to call functions without dots and parentheses, creating more readable code that resembles natural language.
Infix functions must be member functions or extension functions with a single parameter:
infix fun Int.pow(exponent: Int): Int {
var result = 1
repeat(exponent) {
result *= this
}
return result
}
// Usage with infix notation
val result = 2 pow 3 // Same as 2.pow(3)
println(result) // 8
This infix function provides a natural way to express mathematical power operations.
Kotlin provides several built-in infix functions for common operations:
// Pair creation
val coordinate = 10 to 20 // Same as Pair(10, 20)
// Boolean operations
val result = true and false // Same as true.and(false)
// Collection operations
val range = 1 until 10 // Same as 1.until(10)
Local functions are functions defined inside other functions, providing encapsulation and code organization benefits.
fun processUserData(users: List<String>): List<String> {
fun validateUser(user: String): Boolean {
return user.isNotBlank() && user.length >= 2
}
fun formatUser(user: String): String {
return user.trim().lowercase().replaceFirstChar { it.uppercase() }
}
return users
.filter { validateUser(it) }
.map { formatUser(it) }
}
Local functions can access variables from their enclosing scope, making them powerful tools for code organization.
Single expression functions provide a concise syntax for simple functions using the assignment operator:
fun greetUser(name: String): String = "Hello, $name!"
fun calculateArea(radius: Double): Double = 3.14159 * radius * radius
fun isEven(number: Int): Boolean = number % 2 == 0
These single expression functions eliminate the need for curly braces and explicit return statements when the function body contains only one expression.
Tail recursive functions allow you to write recursive algorithms without the risk of stack overflow by using the tailrec modifier:
tailrec fun factorial(n: Long, accumulator: Long = 1): Long {
return if (n <= 1) {
accumulator
} else {
factorial(n - 1, n * accumulator)
}
}
The tailrec modifier optimizes the recursive calls into iterative loops at compile time.
Kotlin functions can have different visibility modifiers that control their accessibility:
// Public function (default)
fun publicFunction() = "Accessible everywhere"
// Private function
private fun privateFunction() = "Only accessible within this file"
// Internal function
internal fun internalFunction() = "Accessible within the same module"
// Protected function (only in classes)
protected fun protectedFunction() = "Accessible in subclasses"
Understanding function visibility is crucial for creating well-structured applications with proper encapsulation.
Here’s a comprehensive example that demonstrates multiple Kotlin function concepts working together:
// Extension function for String validation
fun String.isValidEmail(): Boolean {
val emailRegex = "^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$".toRegex()
return this.matches(emailRegex)
}
// Infix function for creating user data
data class User(val name: String, val email: String, val age: Int)
infix fun String.withEmail(email: String): Pair<String, String> = this to email
// Higher-order function for data processing
inline fun <T, R> List<T>.processAndTransform(
filter: (T) -> Boolean,
transform: (T) -> R
): List<R> {
return this.filter(filter).map(transform)
}
// Main function demonstrating the complete pipeline
fun main() {
// Sample data
val userData = listOf(
"Alice" withEmail "alice@example.com",
"Bob" withEmail "bob.invalid.email",
"Charlie" withEmail "charlie@test.org",
"David" withEmail "david@company.com"
)
// Local function for creating users
fun createUser(data: Pair<String, String>): User? {
val (name, email) = data
return if (email.isValidEmail()) {
User(name, email, (20..60).random())
} else null
}
// Processing pipeline using higher-order functions
val validUsers = userData
.processAndTransform(
filter = { it.second.isValidEmail() },
transform = { createUser(it)!! }
)
// Lambda expression for displaying results
val displayUser: (User) -> String = { user ->
"User(name='${user.name}', email='${user.email}', age=${user.age})"
}
println("Valid Users:")
validUsers.forEach { user ->
println(displayUser(user))
}
// Using function references
val emailList = validUsers.map(User::email)
println("\nEmail addresses: ${emailList.joinToString(", ")}")
}
Expected Output:
Valid Users:
User(name='Alice', email='alice@example.com', age=45)
User(name='Charlie', email='charlie@test.org', age=32)
User(name='David', email='david@company.com', age=28)
Email addresses: alice@example.com, charlie@test.org, david@company.com
This comprehensive example demonstrates:
::
operatorKotlin functions provide incredible flexibility and power for modern application development. By understanding function syntax, lambda expressions, higher-order functions, extension functions, inline functions, and infix functions, you can write more expressive, maintainable, and efficient code.
The fun keyword is your gateway to functional programming in Kotlin, while lambda expressions enable concise and powerful data processing. Higher-order functions promote code reusability, and extension functions allow you to enhance existing classes without modification.