Kotlin - 람다 표현식(Lambda expression)에 대해서 알아보기

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() }

마치 익명함수를 인자로 전달하는 것이 아니라 함수의 구현 내용 처럼 보입니다. 가독성을 높여줄 수 있는 문법입니다. 하지만 함수 구조에 따라서 가독성을 해칠 수도 있습니다. 가독성을 높일 수 있을 때만 사용하면 좋을 것 같습니다.

정리

람다 표현식에 대해서 알아보았습니다. 람다는 간단히 익명함수이며, 함수의 인자와 리턴 타입이 명확할 때 구현 내용만 작성하는 기술입니다.

참고

Loading script...
codechachaCopyright ©2019 codechacha