람다
- 이름이 없는 함수: 익명함수
- 간단한 처리를 하므로 fun으로 함수를 만들기 번거로울 때 사용
- filter, map, forEach 같은 함수 내에서 사용
기본 문법
val lamdaName = { arg1: Type, arg2: Type -> return }
[예시]
val result = listOf(1, 2, 3).map { it * 2 } // it * 2는 람다식
val result = listOf(1, 2, 3).map { it * 2 } // it * 2는 람다식
람다 표기법
- 소괄호로 묶은 파라미터와 리턴, 화살표 표기법으로 연관
// 매개변수 없음
() -> A
// 매개변수 있음
(A, B) -> C
- 파라미터 타입은 생략 가능하나 리턴은 생략 불가능
// 생략 전
(a: Int, b: Int) -> Int
// 생략 후
(a, b) -> Int
// 생략 불가능
(a, b) ->
- 인스턴스화
// 람다식
{a, b -> a + b}
// 확장함수
String::toInt
- 함수가 마지막 파라미터면 블록을 밖으로 내보내기 가능
// block 이 마지막 파라미터 아닐 때
fun greeting(block: (String) -> Any, value: String) {
block(value)
}
greeting({ println(it) }, "hello world!!!") -- X
// block이 마지막 파라미터 일 때
fun greeting(value: String, block: (String) -> Any) {
block(value)
}
greeting("hello world!!!") { println(it) }
Receiver
- 수신객체를 명시하지 않고 람다의 본문 안에서 다른 객체의 메소드를 호출 가능: 수신객체 지정 람다
- 계층 형태의 데이터 구조를 쉽게 생성 가능: builder
- Kotlin DSL(Domain Specific Language) 생성 가능: gradle
- 람다의 파라미터를 감싸던 소괄호를 수신 객체 타입의 오른쪽으로 빼낸 뒤 마침표를 삽입
T.() -> R
[예시]
- apply
@kotlin.internal.InlineOnly
public inline fun <T> T.apply(block: T.() -> Unit): T {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
block()
return this
}
- with
@kotlin.internal.InlineOnly
public inline fun <T, R> with(receiver: T, block: T.() -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return receiver.block()
}
고차함수
- 일급 함수(first-class)
- 변수나 자료구조를 담아낼 수 있으며 파라미터로 다른 함수를 전달하거나 함수를 리턴
- 다른 함수를 인자로 받거나, 함수를 반환하는 함수
- 람다나 함수 참조를 사용하여 함수를 값으로 표현 가능
- 장점
- 재사용성이 높아짐
- 추상화가 쉬어짐
- 강력한 컬렉션 처리(map, filter …) 가능
함수 타입
- 타입 추론으로 변수 타입을 지정하지 않아도 람다를 변수에 대입 가능
val sum = { x: Int, y: Int -> x + y}
val action = { println ("") }
- 함수 타입을 정의하려면 함수 파라미터 타입을 괄호 안에 넣고, 화살표를 추가하여 리턴타입 지정
- 반환 값이 없을 경우 Unit
- 파라미터 지정 시 람다 식에서 파라미터 타입을 선언하지 않아도 됨
val sum: (Int, Int) -> Int = { x, y -> x + y}
-
- null 타입 지정 가능
var canReturnNull: (Int, Int) -> Int? = { x, y -> null }
-
- 파라미터명 지정 가능
// 자동추가 파라미터명 index, s
stringList.forEachIndexed { index, s -> }
// 파라미터명 변경
stringList.forEachIndexed { idx, value -> }
파라미터로 받은 함수 호출
- 일반 함수 호출과 동일: 파라미터 위치와 타입 값 구분
[예시]
fun calculate(operation: (Int, Int) -> Int){
val result = operation(2, 3)
println("계산 결과 = ${result}")
}
calculate { a, b -> a + b }
디폴트 값을 지정한 함수 타입 파라미터
- 디폴트 값을 지정 가능
[예시]
fun <T> Collection<T>.joinToString(
separator: String = ", ",
prefix: String = "",
postfix: String = "",
// 함수 타입 파라미터를 선언하면서 람다를 디폴트 값으로 지정
transform: (T) -> String = { it.toString() }
): String {
val result = StringBuilder(prefix)
for((index, element) in this.withIndex()) {
if(index > 0) result.append(separator)
// transform 파라미터로 받은 함수를 호출
result.append(transform(element))
}
result.append(postfix)
return result.toString()
}
val letters = listOf("Alpha", "Beta")
// 디폴트 변환 함수를 사용
println(letters.joinToString())
// 람다를 인자로 전달
println(letters.joinToString { it.lowercase() })
// 이름 붙인 인자 구문을 사용해 람다를 포함하는 여러 인자를 전달
println(letters.joinToString(separator = "! ", postfix = "! ",
transform = { it.uppercase() }))
널이 될 수 있는 함수 타입 파라미터
- 널이 될 수 있는 함수타입 사용 가능
- 널이 될 수 있는 함수 타입으로 함수를 받으면 함수 직접 호출 불가능
- 명시적으로 null 여부를 지정
[예시]
fun <T> Collection<T>.joinToString(
separator: String = ", ",
prefix: String = "",
postfix: String = "",
// 널이 될 수 있는 함수 타입의 파라미터를 선언
transform: ((T) -> String)? = null
): String {
val result = StringBuilder(prefix)
for((index, element) in this.withIndex()) {
if(index > 0) result.append(separator)
// 안전 호출을 사용해 함수를 호출
val str = transform?.invoke(element)
?: element.toString() // 엘비스 연산자를 사용해 람다를 인자로 받지 않은 경우 처리
result.append(str)
}
result.append(postfix)
return result.toString()
}
함수 반환
- 함수를 인자로 받을 수도 있으며 함수를 반환 가능
- 함수의 반환 타입을 함수 타입으로 지정
[예시]
enum class Delivery { STANDARD, EXPEDITED }
class Order(val itemCount: Int)
fun getShippingCostCalculator(
delivery: Delivery
): (Order) -> Double { // 반환 타입으로 함수 타입을 선언해 함수를 반환하는 함수를 선언
if(delivery == Delivery.EXPEDITED) {
return { order -> 6 + 2.1 * order.itemCount } // 함수에서 람다를 반환
}
return { order -> 1.2 * order.itemCount } // 함수에서 람다를 반환
}
fun main() {
val calculator = getShippingCostCalculator(Delivery.EXPEDITED)
println("Shipping costs : ${calculator(Order(3))}")
}
퀴즈
객관식
1. 다음 중 람다식의 올바른 문법은?
- val f = fun(a: Int) = a * 2
- val f = { a: Int -> a * 2 }
- val f = (a: Int) -> a * 2
- val f = [a:Int] -> a * 2
답: 2
2. 고차 함수란 무엇인가?
- 함수를 반환하는 함수만 의미한다.
- 함수를 인자로 받거나 반환하는 함수를 의미한다.
- 람다식을 의미한다.
- 클래스 내부에 선언된 함수를 의미한다.
답: 2
3. 다음 코드의 출력 결과는?
fun operate(x: Int, y: Int, op: (Int, Int) -> Int): Int {
return op(x, y)
}
fun main() {
val result = operate(4, 5) { a, b -> a + b }
println(result)
}
- 20
- 9
- 45
- 컴파일 에러
답: 2
주관식
1. 람다식을 변수에 할당하고 호출하기
add라는 이름의 람다식을 만들어 두 정수의 합을 반환하게 해봐.
그리고 add를 호출해 3과 7의 합을 출력하시오.
답:
val add = { num1: Int, num2: Int -> num1 + num2 }
println(add(3, 7))
2. 고차 함수를 이용해 리스트 처리하기
applyOperation이라는 함수를 만들어보자.
- 세 개의 매개변수를 받는다: list: List<Int>, op: (Int) -> Int, filter: (Int) -> Boolean
- 함수는 먼저 리스트에서 filter 조건을 만족하는 요소만 걸러내고,
- 그 후 op 함수를 적용해 변환한 결과 리스트를 반환한다.
예를 들어, 짝수만 골라 2배로 만든 리스트를 반환할 수 있어야 한다.
답:
fun applyOperation(list: List<Int>,
op: (Int) -> Int,
filter: (Int) -> Boolean
): List<Int>{
val filtered = list.filter { filter }
val transformed filtered.map { op }
return transformed
}
fun main() {
val result = applyOperation(listOf(1, 2, 3, 4, 5),
filter = { it % 2 == 0 },
op = { it * 2})
println(result)
}
→ 오답 노트:
filter와 op가 호출되지 않음
fun applyOperation(list: List<Int>,
op: (Int) -> Int,
filter: (Int) -> Boolean
): List<Int>{
val filtered = list.filter { filter(it) }
val transformed filtered.map { op(it) }
return transformed
}
fun main() {
val result = applyOperation(listOf(1, 2, 3, 4, 5),
filter = { it % 2 == 0 },
op = { it * 2})
println(result)
}
3. 확장 함수로 람다 활용하기
List<Int>에 대해 sumByLambda라는 확장 함수를 작성해보자.
- 람다 (Int) -> Int를 인자로 받고,
- 리스트 각 요소에 람다를 적용한 후, 그 합을 반환해야 한다.
fun sumByLambda(numbers: List<Int>,
sum: (Int) -> Int
): Int{
val result = numbers.fold { sum }
return result
}
fun main() {
val result = sumByLambba(listOf(1, 2, 3, 4, 5),
sum = { acc, num -> acc + num })
println(result)
}
오답 노트:
- 리시버를 활용하기
- fold는 인자가 두개가 필요함
- 현재 문제는 “그대로” 값을 더하지만 고차함수를 이용하여 다양한 응용 후 합산이 가능
fun List<Int>.sumByLambda(transform: (Int) -> Int): Int {
return this.map { transform(it) }.sum()
}
fun main() {
val result = listOf(1, 2, 3, 4, 5).sumByLambda { it } // 각 요소를 합산
//응용
listOf(1, 2, 3, 4, 5).sumByLambda { it * 2 }
listOf(1, 2, 3, 4, 5).sumByLambda { if (it % 2 == 0) it else 0 }
println(result)
}
추가 학습
람다 정의해보기
1. 두 숫자를 곱한 결과를 반환하는 람다
val multiply: (Int, Int) -> Int = { a, b -> a * b }
2. 문자열을 받아 길이를 반환하는 람다
val strLength: (String) -> Int = { str -> str.length }
3. 정수를 받아 짝수인지 Boolean으로 반환하는 람다
val numJud: (Int) -> Boolean = { num -> if num%2==0 true else false
[보충]
val numJud: (Int) -> Boolean = { num -> num % 2 == 0 }
함수를 인자로 받는 고차함수
정수를 하나 받고, 그 정수에 어떤 연산(함수)을 적용한 결과를 반환하는 함수를 만들기
예시
fun applyOperation(x: Int, operation: (Int) -> Int): Int {
return operation(x)
}
사용 예
val square = { num: Int -> num * num }
println(applyOperation(4, square)) // 16
1. 위와 비슷하게 applyOperation 함수를 만들기
fun applyOperation(x: Int, y: Int, operation: (Int, Int) -> Int): Int {
return operation(x, y)
}
2. 인자로 넘길 함수도 만들어서 테스트 (예: 제곱, 2배 등)
val involution = { num1: Int, num2: Int -> num1.pow(num2) }
[보충] pow()는 Double용 함수이므로 반환형에 위배
2-1. toInt()
val involution = { num1: Int, num2: Int ->
num1.pow(num2).toInt()
}
2-2. 재귀/반복
val involution = { num1: Int, num2: Int ->
var result = 1
repeat(num2) { result *= num1 }
result
}
3. println()으로 결과 출력
println(applyOperation(3, 4, involution))
함수를 만들어서 리턴하는 함수
1. 곱셈 함수 리턴하기
[문제]
함수 getMultiplier를 작성하세요.
이 함수는 정수 n을 입력받아, 또 다른 정수 x를 입력받으면 x * n을 반환하는 함수를 리턴해야 합니다.
[조건]
- 함수 이름: getMultiplier
- 매개변수: n: Int
- 반환값: (Int) -> Int 타입의 함수
[예시]
val double = getMultiplier(2)
println(double(10)) // 20
val triple = getMultiplier(3)
println(triple(10)) // 30
답:
fun getMultiplier (n: Int): (Int) -> Int{
return { x: Int -> x * n}
}
2. 조건 함수 리턴하기
[문제]
함수 getGreaterThan을 작성하세요.
이 함수는 기준값 n을 입력받고, 어떤 정수가 그 값보다 큰지를 판별해주는 함수를 반환합니다.
[조건]
- 함수 이름: getGreaterThan
- 매개변수: n: Int
- 반환값: (Int) -> Boolean 타입의 함수
[예시]
val isAdult = getGreaterThan(19)
println(isAdult(20)) // true
println(isAdult(18)) // false
답:
fun getGreaterThan(n: Int): (Int) -> Boolean{
return { x: Int -> x > n }
}
3. 고차함수 조합하기
[문제]
숫자 리스트와 조건 함수를 받아서 조건에 맞는 숫자만 출력하는 filterAndPrint 함수를 작성하세요.
[조건]
- 함수 이름: filterAndPrint
- 매개변수:
- numbers: List<Int>
- condition: (Int) -> Boolean
- 동작: condition을 만족하는 숫자만 출력 (println() 사용)
[예시]
val numbers = listOf(1, 2, 3, 4, 5, 6)
val evenCondition = { x: Int -> x % 2 == 0 }
filterAndPrint(numbers, evenCondition)
// 출력: 2, 4, 6
답:
fun filterAndPrint(numbers: List<Int>, condition: (Int) -> Boolean){
numbers.forEach{ number ->
if(condition(number)){
println(number)
}
}
}
연산을 매핑한 Map<String, (Int, Int) -> Int>으로 관리하는 방식
1. 연산 기호에 따라 수식 처리하기
[문제]
- 연산 기호(String)와 실제 연산 함수(Int, Int) -> Int를 Map으로 관리한다.
- getOperationFromMap(op: String): (Int, Int) -> Int 함수를 작성해서,
- Map에서 해당 연산을 찾아 반환
- 없으면 예외를 던지거나 기본 연산(예: 덧셈) 반환
[힌트]
val operations = mapOf<String, (Int, Int) -> Int>(
"+" to { a, b -> a + b },
"-" to { a, b -> a - b },
"*" to { a, b -> a * b },
"/" to { a, b -> a / b }
)
fun getOperationFromMap(op: String): (Int, Int) -> Int {
return operations[op] ?: throw IllegalArgumentException("Unknown operation")
}
- getOperationFromMap 함수를 완성해보고,
- main에서 "+" 와 "*" 연산을 각각 가져와 호출해 출력해봐.
답:
val operations = mapOf<String, (Int, Int) -> Int>(
"+" to { a, b -> a + b },
"-" to { a, b -> a - b },
"*" to { a, b -> a * b },
"/" to { a, b -> a / b }
)
fun getOperationFromMap(op: String): (Int, Int) -> Int {
return operations[op] ?: throw IllegalArgumentException("Unknown operation")
}
fun main() {
val plus = getOperationFromMap("+")
val multiply = getOperationFromMap("*")
println(plus(3, 4))
println(multiply(2, 5))
}
2. 사용자로부터 직접 연산 기호 (+, -, *, /) 입력받아서 처리하기
[문제]
- 사용자에게 연산 기호를 입력받고,
- 위 Map에서 연산 함수를 가져와서 적용하는 코드를 작성해보자.
[힌트]
- readLine() 으로 입력받기 (터미널 환경 가정)
- 입력받은 연산 기호를 getOperationFromMap에 넘겨서 함수 받아 사용하기
[예시 흐름]
연산 기호를 입력하세요 (+, -, *, /): *
3 5
결과: 15
답:
val operations = mapOf<String, (Int, Int) -> Int> (
"+" to { a, b -> a + b },
"-" to { a, b -> a - b },
"*" to { a, b -> a * b },
"/" to { a, b -> a / b }
)
fun getOperationFromMap(op: String): (Int, Int) -> Int{
return operations[op] ?: throw IllegalArgumentException("Unkown operation")
}
fun main() {
print("연산 기호를 입력하세요 (+, -, *, /)")
val inputOperation = readLine()!!
val numbers = readLine()!!.split(" ").map{ it.toInt()}
val operation = getOperationFromMap(inputOperation)
println("결과: " + operation(numbers[0], numbers[1]))
}
[보충]
operation과 관련된 변수를 두개나 설정해야하는게 어색함
1. 변수 이름 중복을 줄여서 만들기
val opSymbol = readLine()!!
val operate = getOperationFromMap(opSymbol)
2. 입력과 동시에 함수처리하기
val operate = getOperationFromMap(readLine()!!)
커링(Currying)이란?
- 여러 개의 인자를 한 번에 받는 함수를
- 하나의 인자만 받는 함수들의 연쇄로 바꾸는 기법
-> (A, B) -> C 형태 함수를 (A) -> (B) -> C 형태로 변환
[예시]
fun add(a: Int): (Int) -> Int = { b -> a + b }
fun main() {
val addFive = add(5)
println(addFive(3)) // 8 출력
}
- multiply 커링 함수를 작성
- multiply(a: Int): (Int) -> Int 형태
- 반환 함수는 a와 b를 곱한 결과를 반환
- multiply를 이용하여 구현하기
- val timesTen = multiply(10)을 작성하기
- timesTen(5) 결과를 출력하기
답:
fun multiply(a: Int): (Int) -> Int = { b -> a * b}
fun main(){
val timesTen = multiply(10)
println(timesTen(5))
}
'개발새발 > 코틀린' 카테고리의 다른 글
[코틀린/Kotlin] null처리와 스마트캐스트 (0) | 2025.08.20 |
---|---|
[코틀린/Kotlin] 확장함수 (0) | 2025.08.19 |
[코틀린/Kotlin] 컬렉션 (3) | 2025.08.16 |
[코틀린/Kotlin] 상속과 인터페이스/추상클래스 (2) | 2025.08.13 |
[코틀린/Kotlin] 함수 및 클래스 구조 (1) | 2025.08.12 |