Kotlin 함수와 람다: 함수 선언부터 고차 함수까지
Kotlin은 함수형 프로그래밍을 강력하게 지원합니다. 이 포스트에서는 함수와 람다의 다양한 사용법을 알아보겠습니다.
함수 선언
기본 함수
fun sum(a: Int, b: Int): Int {
return a + b
}
// 사용
val result = sum(1, 2)
기본 인자 (Default Arguments)
fun read(b: Array<Byte>, off: Int = 0, len: Int = b.size) { ... }
가변 인자 (Vararg)
fun foo(vararg strings: String) { /* ... */ }
// 배열 전개
fun(*array)
Unit 반환 타입
반환 값이 없는 함수는 Unit을 반환합니다. 생략 가능합니다.
fun printSum(a: Int, b: Int): Unit {
println("sum of $a and $b is ${a + b}")
}
// Unit 생략 가능
fun printSum(a: Int, b: Int) {
println("sum of $a and $b is ${a + b}")
}
Single-Expression 함수
함수 본문이 단일 표현식인 경우 중괄호를 생략할 수 있습니다.
fun double(x: Int): Int = x * 2
// 반환 타입 추론
fun double(x: Int) = x * 2
fun max(a: Int, b: Int) = if (a > b) a else b
Named Arguments
파라미터 이름을 명시하여 호출할 수 있습니다.
reformat(str, wordSeparator = '_')
// Spread operator
foo(strings = *arrayOf("a", "b", "c"))
Infix 함수 (중간 연산자)
// 선언
infix fun Int.shl(x: Int): Int { ... }
// 사용
1 shl 2 // infix notation
1.shl(2) // 일반 호출
Global 함수
클래스 외부에 선언된 함수는 다른 파일에서 import 없이 사용 가능합니다 (import static과 유사).
Tail Recursive 함수
재귀 함수 최적화를 위해 tailrec 키워드를 사용합니다. JVM에서만 지원됩니다.
tailrec fun findFixPoint(x: Double = 1.0): Double =
if (x == Math.cos(x)) x else findFixPoint(Math.cos(x))
함수 내 함수
함수 내부에 다른 함수를 정의할 수 있습니다.
fun outer() {
fun inner() { ... }
inner()
}
백틱 함수명
테스트에서 읽기 쉬운 이름을 사용할 수 있습니다.
fun `should return true when valid input`() {
// test code
}
Inline 함수
람다를 인라인으로 처리하여 오버헤드를 줄입니다.
inline fun <T> lock(lock: Lock, body: () -> T): T { ... }
// noinline: 특정 람다는 인라인하지 않음
inline fun foo(inlined: () -> Unit, noinline notInlined: () -> Unit) { ... }
crossinline
람다가 다른 컨텍스트에서 호출될 때 사용합니다.
inline fun f(crossinline body: () -> Unit) {
val f = object : Runnable {
override fun run() = body()
}
}
Inline 프로퍼티
val foo: Foo
inline get() = Foo()
var bar: Bar
get() = ...
inline set(v) { ... }
inline var bar: Bar
get() = ...
set(v) { ... }
Reified 타입
inline 함수에서 제네릭 타입 정보를 런타임에 사용할 수 있습니다.
inline fun <reified T> TreeNode.findParentOfType(): T? {
var p = parent
while (p != null && p !is T) {
p = p.parent
}
return p as T?
}
// 사용
treeNode.findParentOfType<MyTreeNode>()
object YamlReader {
inline fun <reified T> read(path: String): T =
ObjectMapper(YAMLFactory()).readValue(File(path), T::class.java)
}
람다 표현식
람다 타입 선언
인터페이스 선언 없이 함수 타입을 정의할 수 있습니다.
val lambda: (String) -> String
val sum: (Int, Int) -> Int = { x, y -> x + y }
// 익명 함수
ints.filter(fun(item) = item > 0)
함수를 파라미터로 받기
fun <T> lock(lock: Lock, body: () -> T): T {
lock.lock()
try {
return body()
} finally {
lock.unlock()
}
}
// 함수 참조로 전달
fun toBeSynchronized() = sharedResource.operation()
val result = lock(lock, ::toBeSynchronized)
람다 사용법
// 명시적 파라미터
{ arg -> arg + 1 }
// 암시적 파라미터 (it)
fruits
.filter { it.startsWith("a") }
.sortedBy { it }
.map { it.toUpperCase() }
.forEach { println(it) }
// 다중 파라미터
max(strings, { a, b -> a.length < b.length })
// 사용하지 않는 변수
map.forEach { _, value -> println("$value!") }
람다와 return
람다 내부의 return은 해당 람다를 포함한 함수를 종료시킵니다 (inline 함수의 경우).
// 암시적 반환 (마지막 표현식이 반환값)
ints.filter {
val shouldFilter = it > 0
shouldFilter
}
// 명시적 반환
ints.filter {
val shouldFilter = it > 0
return@filter shouldFilter
}
// 라벨 사용
ints.forEach lit@ {
if (it == 0) return@lit
print(it)
}
// 암시적 라벨
ints.forEach {
if (it == 0) return@forEach
print(it)
}
Destructuring 람다
{ a -> ... } // 단일 파라미터
{ a, b -> ... } // 두 파라미터
{ (a, b) -> ... } // 구조 분해 (Pair 등)
{ (a, b), c -> ... } // 구조 분해 + 추가 파라미터
Receiver가 있는 람다
fun <T> T.apply(block: T.() -> Unit): T { /* ... */ }
class HTML {
fun body() { ... }
}
fun html(init: HTML.() -> Unit): HTML {
val html = HTML()
html.init()
return html
}
// 사용
html {
body() // receiver 객체의 메서드 직접 호출
}
표준 라이브러리 함수
let
it을 통해 값을 참조하고 결과를 반환합니다.
val mapped = value?.let { transformValue(it) } ?: defaultValueIfValueIsNull
apply
객체의 메서드를 호출하고 객체 자체를 반환합니다.
IntArray(size).apply { fill(-1) }
with
한 객체의 여러 메서드를 호출할 때 유용합니다.
class Turtle {
fun penDown()
fun penUp()
fun turn(degrees: Double)
fun forward(pixels: Double)
}
val myTurtle = Turtle()
with(myTurtle) {
penDown()
for (i in 1..4) {
forward(100.0)
turn(90.0)
}
penUp()
}
also
객체로 각기 다른 작업을 수행할 때 사용합니다.
val numbers = mutableListOf(1, 2, 3)
numbers.also { println("The list elements: $it") }
.add(4)
run
with와 유사하지만 확장 함수 형태입니다.
val result = listOf.run { add(1); get(0) }
takeIf
조건에 맞는 경우에만 처리합니다.
val number = Random.nextInt(100)
val evenOrNull = number.takeIf { it % 2 == 0 }
Sequence 빌더
무한 시퀀스를 생성할 수 있습니다.
fun main(args: Array<String>) {
val fibonacciSeries = sequence {
var a = 0
var b = 1
yield(a)
yield(b)
while (true) {
val c = a + b
yield(c)
a = b
b = c
}
}
println(fibonacciSeries.take(10).joinToString(","))
}
컬렉션 함수
리스트 생성
listOf("apple", "banana", "kiwi") // 불변 리스트
mutableListOf(1, 2, 3) // 가변 리스트
다음 단계
함수와 람다에 대해 알아보았습니다. 다음으로 제어문에 대해 알아보세요.
Comments