Lambda expression은 람다식(람다표현식)이라고 번역되고, 간단히 람다라고도 합니다. 쉽게 말하면 람다는 익명함수입니다. 익명함수는 함수의 이름이 없는 함수를 말합니다. 보통 한번 사용되고 재사용되지 않는 함수를 만들때 익명함수로 만듭니다. 굳이 함수를 따로 생성하지 않고, 코드 중간에 익명함수를 만들 수 있거든요. 이러면 코드 가독성이 높아지고, 함수형 프로그래밍에서 자주 사용하는 패턴입니다.
익명함수를 알아보기 전에 일급함수(First class functions)와 고차함수(Higher order functions)라는 개념에 대해서 알고 있으면 좋습니다.
익명함수 생성
익명함수는 아래처럼 이름없이 정의되는 함수를 말합니다. 변수 greeting에 할당되는 내용이 익명함수입니다.
// 익명함수를 생성하여 greeting에 할당
val greeting = fun(){ println("Hello") }
// 익명함수 호출
greeting("chacha")
실행 결과
Hello
람다를 이용하면 더 간단히 익명함수를 정의할 수 있습니다. 아래 코드는 위의 코드를 람다로 다시 작성한 코드입니다.
// 익명함수를 생성하여 greeting에 할당
val greeting: () -> Unit = { println("Hello") }
// 익명함수 호출
greeting()
인자를 받고, 값을 리턴하는 익명함수
위의 코드는 매우 간단한 익명함수입니다. 인자와 어떤 값을 리턴하는 익명함수는 어떻게 만들까요?
아래 코드는 String 타입의 인자 2개를 받고 String을 리턴해주는 익명함수입니다.
fun main(args: Array<String>) {
// 익명함수를 생성하여 greeting에 할당
val greeting2 = { name: String, age:String -> "Hello. My name is $name. I'm $age year old" }
// 익명함수 호출
val result = greeting2("chacha", "5")
println(result)
}
위의 코드에서 ->
왼쪽에 인자의 이름과 타입을 명시해줘야 합니다.
name: String, age:String
는 name과 age라는 String을 인자로 받겠다는 의미입니다.
리턴 값은 ->
의 오른쪽 코드 중 가장 마지막 코드가 암시적으로 리턴 값이 됩니다. 명시적으로 return
을 적지 않습니다.
이 예제에서는 "Helo. My name ..."이라는 String이 리턴되게 됩니다.
실행 결과
Hello. My name is chacha. I'm 5 year old
인자 타입을 생략하는 익명함수
바로 위의 예제에서는 익명함수의 인자를 선언할 때 이름과 타입을 명시해줬습니다. 만약 익명함수가 할당되는 변수에 인자와 리턴 타입이 정의되어있다면 이를 생략할 수 있습니다.
아래 코드는 위의 코드를 조금 바꾼 것입니다. 이전과 다르게 greeting2
는 (String, String) -> String
라는 함수의 타입이 정의되어있습니다.
그렇기 때문에 익명함수에서 인자의 타입을 생략할 수 있습니다. 컴파일러가 이미 알고 있기 때문입니다.
fun main(args: Array<String>) {
// 익명함수를 생성하여 greeting에 할당
val greeting2: (String, String) -> String = { name, age -> "Hello. My name is $name. i'm $age year old" }
// 익명함수 호출
val result = greeting2("chacha", "5")
println(result)
}
인자 선언을 생략할 수 있는 익명함수
익명함수의 인자가 1개인 경우, name과 같은 인자 이름을 생략할 수 있습니다.
아래 코드는 인자 name
1개를 받는 익명함수입니다.
val greeting2: (String) -> String = { name -> "Hello. My name is $name."}
val result = greeting2("chacha")
인자가 1개일 때 선언을 생략할 수 있으며 인자에 접근하려면 it
라는 이름으로 접근해야 합니다.
인자를 생략한 코드는 다음과 같습니다. 인자 name
을 생략했고 대신 it
로 인자에 접근하고 있습니다.
val greeting2: (String) -> String = { "Hello. My name is $it."}
val result = greeting2("chacha")
it
는 iterator를 의미합니다. 코틀린은 인자를 생략하는 경우 it
로 인자에 접근할 수 있게 합니다.
라이브러리에서 사용되는 익명함수
모든 것을 생략할 수 있었던 것은 변수에 이미 인자와 리턴 타입이 정의되어있기 때문입니다. 컴파일러가 모든 정보를 알고 있기 때문에 생략해도 빌드 에러가 발생하지 않을 수 있었습니다.
코틀린에서 여러 객체들은 함수를 인자로 받는 함수들이 많습니다. 익명함수를 사용하면 아래처럼 짧은 코드로 쉽게 구현할 수 있습니다.
val numbers = listOf<Int>(5, 1, 3, 2, 9, 6, 7, 8, 4)
println(numbers)
val sorted = numbers.sortedBy({ it })
println(sorted)
val biggerThan5 = numbers.sortedBy({ it }).filter({ it > 5 })
println(biggerThan5)
실행 결과
[5, 1, 3, 2, 9, 6, 7, 8, 4]
[1, 2, 3, 4, 5, 6, 7, 8, 9]
[6, 7, 8, 9]
sortedBy({ it })
에서 익명함수는 { it }
입니다. 함수의 정의를 보면 Int를 인자로 받도록 정의되어있을 것입니다.
filter({ it > 5 })
의 내부도 익명함수입니다.
위와 같은 패턴으로 익명함수를 사용하는 이유는 무엇일까요?
구현내용은 거의 비슷하지만 오름차순 정렬
, 내림차순 정렬
처럼 크기를 비교하는 코드만 다른 경우,
이 부분을 익명함수로 받아 중복을 제거할 수 있기 때문입니다.
SAM(Single Abstract Method)
SAM는 Single Abstract Method의 약자입니다. 추상 메소드에 하나의 메소드만 있는 것을 SAM이라고 합니다. 람다 표현식으로 SAM의 익명객체를 만들어 인자로 넘길 수 있습니다. 위의 익명 함수와 유사합니다. (SAM은 자바의 Functional interface 비슷합니다. 인터페이스에 하나의 메소드만 있는 것을 말합니다.)
아래코드는 안드로이드에서 흔하게 쓰이는, 익명 객체 패턴입니다.
setOnClickListener
는 익명 클래스를 인자로 받습니다.
4줄의 익명 클래스 코드에서 실질적인 구현 코드는 doSomething
뿐이지만 클래스를 정의하기 위해 형식적인(boilerplate) 코드를 써줘야 합니다.
button.setOnClickListener(object : OnClickListener{
override fun onClick(view: View){
doSomething()
}
})
람다를 이용하면 구현 코드만 작성하여 인자로 넘겨줄 수 있습니다. 인자의 타입은 이미 정의되어있기 때문에, 타입에 맞게 람다 표현식만 작성하면 컴파일러가 알아서 익명객체를 만들어줍니다.
아래 코드는 람다로 익명클래스로 인자를 생성하는 코드입니다.
fun setOnClickListener(listener: (View) -> Unit)
button.setOnClickListener({ view -> doSomething() })
또한, 컴파일러는 인자의 타입을 이미 알고 있기 때문에 아래와 같이 인자를 생략할 수 있습니다.
button.setOnClickListener() { doSomething() }
익명함수, 클래스의 가독성을 높여주는 함수
코틀린은 아래 코드를 더 간단히 쓸 수 있는 문법을 지원합니다.
button.setOnClickListener() { doSomething() }
마지막 인자가 익명함수 또는 익명 클래스이면 ()
를 생략할 수 있습니다.
그래서 아래처럼 쓸 수 있습니다.
button.setOnClickListener { doSomething() }
마치 익명함수를 인자로 전달하는 것이 아니라 함수의 구현 내용 처럼 보입니다. 가독성을 높여줄 수 있는 문법입니다. 하지만 함수 구조에 따라서 가독성을 해칠 수도 있습니다. 가독성을 높일 수 있을 때만 사용하면 좋을 것 같습니다.
정리
람다 표현식에 대해서 알아보았습니다. 람다는 간단히 익명함수이며, 함수의 인자와 리턴 타입이 명확할 때 구현 내용만 작성하는 기술입니다.
참고
Related Posts
- Kotlin - 배열에서 최소 값, 최대 값 찾기
- Kotlin - 2차원 배열 선언, 초기화 방법
- Kotlin - 배열 선언, 초기화 방법
- Kotlin - 리스트, 배열 길이 가져오기
- Kotlin - 리스트에서 최대, 최소 값 찾기
- Kotlin - for 반복문, 배열/리스트 순회
- Kotlin - Timer, 주기적으로 함수 실행
- Kotlin - sleep, 쓰레드 몇 초 지연
- Kotlin - Thread 생성 및 실행
- Kotlin에서 정규표현식 사용하기
- Kotlin - 문자열 길이 계산
- Kotlin - 문자열 비교 방법(equals, ==, compareTo)
- Kotlin - 2개의 배열 하나로 합치기
- Kotlin - 2개의 List 하나로 합치기
- Kotlin - 디렉토리의 모든 파일 리스트 출력
- Kotlin - 리스트 정렬 방법 (sort, sortBy, sortWith)
- Kotlin - 문자열 뒤집기 (Reverse String)
- Kotlin - 랜덤 숫자 생성 (Random, SecureRandom)
- Kotlin - Range, 숫자 범위 표현
- Kotlin - 음수를 양수로 변환, math.abs()
- Kotlin - List를 Set로 변환
- Kotlin - Set를 List로 변환
- Kotlin - 문자열에서 숫자(int)만 추출하는 방법
- Kotlin - Map을 List로 변환하는 방법
- Kotlin - File, Directory가 존재하는지 확인
- Kotlin - List를 Map으로 변환
- Kotlin - List의 중복 요소 제거
- Kotlin - List를 Array로 변환
- Kotlin - 엘비스 연산자 (Elvis Operation)
- Kotlin - Array를 List로 변환
- Kotlin - String을 Float으로 변환
- Kotlin - String을 Double으로 변환
- Kotlin - String을 Int로 변환
- Kotlin - String을 Long으로 변환
- Kotlin - String Null 또는 Empty 체크