프로그래밍 노트/Kotlin

[Kotlin] 코틀린 람다 맛보기

깡냉쓰 2020. 12. 6. 23:34
728x90
반응형

코틀린 람다 맛보기

컬렉션에서 직접 탐색하기

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

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

>> val people = listOf(Person("Corn", 31), Person("HB", 27))
>> findTheOldest(people)
Person(name=Corn, age=31)

경험이 많은 개발자라면 순식간에 이런 루프를 작성할 수 있으나 이 루프에는 상당히 많은 코드가 들어있기 때문에 작성하다 실수를 저지르기 쉽다. 코틀린에서는 더 좋은 방법이 있다. 라이브러리 함수를 쓰면 된다.

// 모든 컬랙션에 대해 maxBy 함수를 호출할 수 있다.
println(people.maxBy{ it.age })

{ it.age } 는 바로 비교에 사용할 값을 돌려주는 함수다. 이 코드는 컬렉션 원소를 인자로 받아서(it이 그 인자)비교에 사용할 값을 반환한다.
이런식으로 단지 함수나 프로퍼티를 반환하는 역할을 수행하는 람다는 멤버 참조로 대치할 수 있다.

people.maxBy(Person::age)

람다식의 문법

람다는 값처럼 여기저기 전달할 수 있는 동작의 모음이다.
람다를 따로 선언해서 변수에 저장할수도 있지만 함수에 인자로 넘기면서 바로 람다를 정의하는 경우가 대부분이다. 아래는 람다 식을 선언하기 위한 문법이다.

람다문법


코틀린 람다 식은 항상 중괄호로 둘러싸여 있고, 화살표(→)가 인자 목록과 람다 본문을 구분해준다.

>>> val sum = {x: Int, y: Int -> x+y}
>>> println(sum(1, 2))
3
// 원한다면 람다 식을 직접 호출해도 된다.
>>> { println(42) }()
// run은 인자로 받은 람다를 실행해 준다.
>>> run { println(42) }

람다식 문법 정리 아래의 람다식은 모두 같은 역할을 수행한다.

// 선언
val sum = { x: Int, y: Int -> x + y }
// 람다식 호출
val peopole = listOf(Person("Alice", 29), Person("Bob", 31))
people.maxBy({ p: Person -> p.age })
// 함수 호출 시 맨 뒤에 있는 인자가 람다 식이라면 그 람다를 괄호 밖으로 빼낼 수 있는 문법 관습이 있음
people.maxBy() { p: Person -> p.age }
// 람다가 어떤 함수의 유일한 인자이고 괄호 뒤에 람다를 썼다면 호출 시 빈 괄호를 없애도 됨
people.maxBy { p:Person -> p.age }
// 람다 파라미터가 하나이고, 타입을 추론할 수 있는 경우
people.maxBy{ it.age }

람다식 내에서의  변수 접근

람다를 함수 안에서 정의하면 함수의 파라미터뿐 아니라 람다 정의의 앞에 선언된 로컬 변수까지 람다에서 모두 사용할 수 있다.

  • 자바
    • 람다식 내에서 외부의 파이널 변수에서만 접근 가능
  • 코틀린
    • 람다식 내에서 외부의 로컬 변수에 접근 && 변경 가능

예제 코드를 살펴보자. 다음 리스트는 전달받은 상태 코드 목록에 있는 클라이언트와 서버 오류의 횟수를 세는 코드이다.

fun printProblemCounts(responses: Collection<String>){
    var clientErrors = 0
    var serverErrors = 0
    responses.forEach{
        if(it.startsWith("4")){
            clientErrors++
        }else if(it.startsWith("5")){
            serverErrors++
        }
    }
    println("$clientErrors client erros, $serverErrors server errors")
}

fun main() {
    val responses = listOf("200 OK", "418 I'm a teapot", "500 Internal Server Error")
    printProblemCounts(responses)
}

이 처럼 람다 안에서 사용하는 외부 변수를 '람다가 포획(capture)한 변수'라고 부른다.

포획한 변수가 있는 람다를 저장해서 함수가 끝난 뒤에 실행해도 람다의 본문 코드는 여전히 포획한 변수를 읽거나 쓸 수 있다. (로컬 변수의 생명주기가 함수가 반환되면 끝나는게 아니다. 로컬 변수를 포획한 람다를 반환한다면..)

멤버 참조

람다로 넘기려는 코드가 이미 함수로 선언된 경우는 어떻게 할까? 코틀린에서는 자바 8과 마찬가지로 함수를 값으로 바꿀 수 있다. 이때 이중 콜론(::)을 사용한다.

val getAge = Person::age

::를 사용하는 식을 멤버 참조(member reference)라고 부른다.

people.maxBy(Person::age)
people.maxBy{ p -> p.age }
people.maxBy{ it.age }

최상위에 선언된 함수나 프로퍼티를 참조할 수도 있다.

fun salute() = println("Salute!")
>>> run(::salute)
Salute!

생성자 참조(constructor reference)를 사용하면 클래스 생성 작업을 연기하거나 저장해둘 수 있다. :: 뒤에 클래스 이름을 넣으면 생성자 참조를 만들 수 있다.

data class Person(val name: String, val age: Int)
// Person 의 인스턴스를 만드는 동작을 값으로 저장한다.
>>> val createPerson = ::Person
>>> val p = createPerson("Alice", 29)
>>> println(p)
Person(name=Alice, age=29)

확장 함수도 멤버 함수와 똑같은 방식으로 참조 가능

fun Person.isAdult() = age >= 21
var predicate = Person::isAdult

 

728x90
반응형