본문으로 바로가기

[Kotlin] 코틀린 람다 #1 - 기본 문법

category 개발이야기/Kotlin 2018. 4. 25. 00:55
반응형

이 글은 Kotlin In Action을 참고 하였습니다.

더욱 자세한 설명이나 예제는 직접 책을 구매하여 확인 하시기 바랍니다

코틀린의 람다는 자바8의 람다와 개념은 매우 비슷합니다.

다만 표현하는 방식이 살짝 다를 뿐이라서 자바8의 기본적인 람다 사용법에 대한 이해가 있다는 가정하에 설명합니다.

자바의 lambda에 대한 글은 http://tourspace.tistory.com/3?category=788398 를 먼저 읽어보고 오길 추천드립니다.


람다식은 자바8부터 사용이 가능합니다.

또한 안드로이드에서 제대로된 functional interface를 이용하려면 N OS 이상이어야만 합니다.

코틀린에서는 위 제한과 상관없이 사용할 수 있다는 점이 가장 큰 매력이 아닐까 싶습니다.

(한 4년쯤 지나고 나면 아마도 모두 자바8이상과 min sdk를 N OS 이상으로 쓰고 있지 않을까 싶습니다만..ㅎㅎ)


추가적으로 람다식은 기본적인 편리한 lambda api들을 제공합니다.

IDE의 어떤한 로직을 구현할때 자동완성기능을 이용하여 먼저 해당 api를 찾아보고 사용한다면 훨씬더 간결하고 직관적인 코드를 짤 수 있습니다.


5.1.2 lambda and collection

data class Person(val name: String, val age: Int)

fun findTheOldest(people: List) {
    var maxAge = 0
    var theOldest: Person? = null
    for (person in people) {
        if (person.age > maxAge) {
            maxAge = person.age
            theOldest = person
        }
    }
    println(theOldest)
}

fun main(args: Array) {
    val people = listOf(Person("Alice", 29), Person("Bob", 31))
    findTheOldest(people)
}

Person이란 객체를 만들고 가장 나이가 많은 한사람을 뽑는 코드 입니다.

사실 구현하기엔 어렵지 않으며, 너무나도 많이 사용하는 코드 패턴입니다.


fun main(args: Array) {
    val people = listOf(Person("Alice", 29), Person("Bob", 31))
    println(people.maxBy {it.age})
}

코틀린에는 maxBy란 collection의 확장함수를 제공합니다.

따라서 위 코드처럼 findTheOldest()를 구현하지 않고도 간단하게 한줄로 표현할 수 있습니다.


5.1.3 Lambda expression

람다식의 문법은 자바와 크게 다르지 않습니다.
fun main(args: Array) {
    val sum = { x: Int, y: Int -> println("Computing the sum of $x and $y...")
       x + y
    }
    println(sum(1, 2))
}
  1. 중괄호로 감싼다. { .. }
  2. 인자와 본문은 ->로 구분한다.
  3. 인자는 ()로 감싸지 않는다.
  4. 인자는 형식추론이 가능하므로 타입을 생략할 수 있다.
  5. 변수에 람다식을 담는경우에는 인자의 타입을 생략할 수 없다.
위 코드에서는 람다식을 변수에 담았습니다.
자바와 크게 다르지 않습니다.
val sum = { x: Int, y: Int -> x + y }
println(sum(1, 2))

{ println(42) }()

run { println(42) }

람다식을 변수에 넣어 호출할 수도 있지만 {} 함수내에 넣고 ()를 이용하여 바로 실행할 수도 있습니다.

단 이때는 run keyword를 사용하는게 가독성에 더 좋습니다.


코드의 간결성을 위해 아래와 같은 규칙도 존재합니다.

  1. 함수의 맨 마지막 인자가 람다라면 () 안에서 빼내서 밖에 람다를 표현할 수 있다.
  2. 인자가 하나라면 그 인자는 람다식 내부에서 it으로 받을 수 있다.
  3. 인자가 하나이면서 그 인자가 람다타입 이라면 ()를 생략할 수 있다.
people.maxBy ({p: Person -> p.age})
people.maxBy () {p: Person -> p.age}
people.maxBy {p: Person -> p.age}
people.maxBy {it.age}
위 네가지의 코드는 모두 같은 표현입니다.
it은 코드를 간결하게 만들어주지만 중첩되는 경우, 헷깔릴 수 도 있으니 따로 명명하여 사용하는걸 권장합니다.


5.1.4 Lambda의 closure

코틀린의 람다는 자바의 closure와 개념이 좀 다릅니다.
자바에서는 lambda가 함수내부에서 실행될때, 로컬 변수에 접근하기 위해서는 해당 변수의 값이 final이어야만 합니다.
이는 stack 영역에 로컬변수의 메모리가 잡히고, 함수의 소멸과 함께 stack에서 날아가기 때문입니다.
final인 경우 해당 변수값을 복사해서 lambda 내부에서 사용하며, 이를 lambda capturing이라고 합니다.

코틀린에서는 final이 아닌 로컬변수에 접근하기 위한 약간의 꼼수가 발휘됩니다.
final 변수의 경우 똑같이 복사하여 lambda 코드와 함께 저장됩니다.
다만 final이 아닌 변수의 경우 특별한 wrapper(class)에 변수로 담고, 그 wrapper를 final 변수에 담아 이 변수의 참조값을 람다 코드와 함께 저장합니다.
따라서 람다에서 이 변수에 항상 접근하여 읽고 쓰기가 가능해 집니다.

fun lambdaSample() {
    var counter = 0
    val inc = {counter++}
    run {println(Inc)}
}

counter는 var로 final 변수가 아니지만 위 코드는 정상 동작 합니다.

위 코드는 사실 아래와 같이 class로 wrapping되어 실행 됩니다.

class Ref<T>(var value: T)

fun lambdaSample() {
    val counterWrapper = Ref(0)
    val inc = {counterWrapper.value++}
    run {println(Inc)}


lambda의 closure에 동작방식에 대해 이해 했다면, 아래 코드의 return 값은 항상 0 이란걸 이해해야 합니다.

(그렇지 않으면, 나중에 람다식의 잘못된 closure 사용으로 코드가 원하는대로 동작하지 않을 수 있습니다.)

fun clickCount(button: Button): Int {
    var clicks = 0
    button.onclick { clicks++ }
    println(clicks)
}


5.1.2 member reference

자바8에서의 method reference와 같은 개념으로 보면 이해하기 쉽습니다.
(사실 자바8과 똑같습니다.)

람다를 넘길때 property나 다른 함수가 같은 signature를 가지고 있다면 간단하게 ::로 표현할 수 있습니다.

::를 사용하여 표현하는 방법은 총 네가지 입니다.

5.1.2.1 클래스의 멤버 표현

people.maxBy {Person::age}

표현식) 클래스이름::멤버변수


5.1.2.2 최상위 함수의 표현

// 최상위 함수
fun showYourName() = println("HongGilDong")

run(::showYourName)

표현식) ::최상위함수

5.1.2.3 변수에 람다대신 member reference 저장

val action1 = {person:Person, msg:String -> sendMail(person. msg)}
val action2 = ::sendMail   

표현식) ::함수


5.1.2.4 생성자
data class Person(val name: String, val age: Int)

fun main(args: Array) {
    val createPerson = ::Person
    val p = createPerson("Alice", 29)
    println(p)
}
생성자를 변수에 저장할 수 있고, 생성을 지연(late initialize) 시킬수 있습니다.
표현식) ::클래스

5.1.2.5 Extension function

확장함수도 람다식을 이용하여 표현할 수 있습니다.

//확장함수
fun Person.isAdult() = this.age >= 20

val predicate = Person::isAdult

표현식) 클래스::함수


5.1.2.6 bound member

val p = Person("Kim", 37)

val ageFun1 = Person::age
println(ageFun1(p)) // 기본 receiver 객체 필요

val ageFun2 = p:age
println(ageFun1()) // 기본 receiver 객체 불필요 (바운드 멤버 참조)

표현식) 객체::함수

반응형