[kotlin] 5장 람다로 프로그래밍

김우경·2022년 1월 27일
0

코액션

목록 보기
3/3
💡 람다식이란? 메소드를 하나의 식으로 표현 → 1) 메소드의 매개변수로 전달, 2) 메소드의 결과로 반환
  • 함수명을 선언하고 사용하는 것이 아닌 식별자 없이 실행가능한 함수
  • 공통 코드 구조를 라이브러리 함수로 쉽게 뽑아낼 수 있음
  • 코틀린 표준 라이브러리에서 많이 사용

5.1 람다 식과 멤버 참조

5.1.1 코드 블록을 인자로 넘기기

람다 도입 이전

: 내부 익명 클래스를 사용

button.setOnClickListner(new OnClickListener() {
	@Override
	public void onClick(View view) {
		//
	}
}

→ 코드가 번잡스럽고, 이런 작업을 여러번 수행해야 하는 경우엔..

→ 일회용 클래스

람다 도입 이후

: 함수를 값처럼 다루자!

button.setOnClickListner{ // }

5.1.2 람다와 컬렉션

람다 없이

data class Person(val name: String, val age: Int) {
    companion object {}
}

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

fun main() {
    val people = listOf(Person("Alice", 29), Person("Bob", 24), Person("Carol", 39))
    findTheOldest(people)
}

람다 활용하기

data class Person(val name: String, val age: Int) {
    companion object {}
}

fun main() {
    val people = listOf(Person("Alice", 29), Person("Bob", 24), Person("Carol", 39))
    println(people.maxByOrNull{ it.age })
    println(people.maxByOrNull(Person::age))
}

→ 책에는 maxBy()로 나오는데 이건 deprecated

→ 람다나 멤버 참조를 사용해서 더 짧고 가독성 좋게 개선

5.1.3 람다 식의 문법

val sum = { x: Int, y: Int -> x + y }
println(sum(1, 2))
  1. 항상 중괄호 사이에 위치한다
  2. 인자 목록에 괄호 필요 x
  3. 람다식을 변수에 저장할 수 있음 → runCatching에서 onSuccess, onFailure

run

: 코드의 일부분을 블록으로 둘러싸서 실행이 필요한 경우

val num = 42
num.run { println(this) }

→ 기본 람다를 언제 사용해야 할지?

https://medium.com/@limgyumin/코틀린-의-apply-with-let-also-run-은-언제-사용하는가-4a517292df29

https://kotlinlang.org/docs/scope-functions.html#function-selection

위의 코드 정식으로 람다 활용하기

people.maxByOrNull({ p: Person -> p.age })
people.maxByOrNull(){ p: Person -> p.age }
people.maxByOrNull { p: Person -> p.age }
people.maxByOrNull{ it.age }  // 제일 익숙한 형태

→ 코틀린에서 맨 뒤의 인자가 람다면 밖으로 뺄 수 있고, 다른 인자가 없으면 () 생략 가능

를 활용해보자

val name = people.joinToString(separator = " ", transform = {p->p.name})
val name = people.joinToString(" "){ p->p.name }

파라미터 타입 추론이 필요할 때

코틀린 컴파일러가 자동으로 파라미터 타입을 추론하여 굳이 명시하지 않아도 되지만, 변수에 저장할때는 타입 추론 문맥이 없어서 명시해야됨

val getAge = { p: Person -> p.age }

5.1.4 현재 영역에 있는 변수 접근

fun printMessageWithPrefix(responses: Collection<String>, prefix: String) {
    var clientErrors = 0
    var serverErrors = 0

    responses.forEach {
        if (it.startsWith("4")) clientErrors++
        else if (it.startsWith("5")) serverErrors++
    }
    println("$prefix $clientErrors client errors, $prefix  $serverErrors server errors")
}

fun main() {
    val errors = listOf("401 Unauthorized", "404 Not Found", "200 Ok", "500 Internal Server Error")
    printMessageWithPrefix(errors, "Errors:")
}

포획 변수 (captured variable)

  • 위 코드에서 prefix, clientErrors, serverErrors와 같이 람다 안에서 사용되는 외부 변수
  • 포획 변수의 생명 주기 : 일반적으로 함수 안의 로컬 변수는 함수가 반환되면 생명 주기가 끝나지만, 함수가 해당 로컬 변수를 포획한 람다를 반환하거나 변수에 저장하면 함수가 끝난 뒤에도 여전히 포획 변수를 사용할 수 있음
class Button {
    fun onClick(function: () -> Int) {
        function()
    }
}

fun tryToCountButtonClicks(button: Button): Int {
    var clicks = 0
    button.onClick{ clicks++ }
    return clicks
}

fun main() {
    val button = Button()
    val clickNum = tryToCountButtonClicks(button)
    println(clickNum)
}

→ 책에서의 설명(tryToCountButtonClicks가 clicks를 반환 한 다음 tryToCountButtonClicks이 호출되어 항상 0이 반환된다고 하는데 실제로 디버깅하면 반환 전에 onClick()이 먼저 호출된 다음 반환이 된다. 따라서 위와 같이 실행하면 1이 출력되는데 요건 번역 오류인지 무슨 의미인지 잘 모르겠다

5.1.5 멤버 참조

people.maxByOrNull{ it.age }

→ 최상위에 선언된 함수나 프로퍼티도 참조 가능하다

fun salute() = println("salute!")

fun main() {
    run(::salute)
}

인자가 여러개인 경우

: 직접 위임 함수에 대한 참조 제공하면 편함

fun sendEmail(person: Person, message: String) {
    println("${person.name} sent $message")
}

fun main() {
    val action1 = {
            person: Person, message: String -> sendEmail(person, message)
    }
    val action2 = ::sendEmail //람다 대신 멤버 참조 사용

    action1(Person("leean", 25), "hi")
    action2(Person("leean", 25), "hi")
}

5.2 컬렉션 함수형 API

5.2.1 filter와 map

filter

val list = listOf(1, 2, 3, 4)
println(list.filter { it%2 == 0 })

→ 컬렉션에서 원하는 원소만 필터해서 새 컬렉션 반환

map

val list = listOf(1, 2, 3, 4)
println(list.filter { it%2 == 0 })

→ 컬렉션에서 주워진 람다를 각 컬렉션에 적용한 결과를 새 컬렉션으로 반환

// 위의 코드에서 불필요한 연산 제거하기
val maxAge = people.maxBy(Person::age)!!.age
people.filter{ it.age == maxAge }

5.2.2 all, any, find

  • count: 조건을 만족하는 원소의 개수 반환
  • find: 조건을 만족하는 첫번째 원소 반환
  • all: 모든 원소가 만족하는지 여부 체크
  • any: 하나라도 만족하는지 여부 체크
val canBeInClub27 = { p:Person -> p.age <= 27 }
val people = listOf(Person("Alice", 27), Person("Bob", 25), Person("Carol", 37))

println(people.all(canBeInClub27))
println(people.any(canBeInClub27))
println(people.count(canBeInClub27))
println(people.find(canBeInClub27))

>>> false
>>> true
>>> 2
>>> Person(name=Alice, age=27)

5.2.3 groupBy

원소를 특성에 따라 그룹으로 나누기

val people = listOf(Person("Alice", 27), Person("Bob", 25), Person("Carol", 27))
println(people.groupBy { it.age })

>>> {27=[Person(name=Alice, age=27), Person(name=Carol, age=27)], 25=[Person(name=Bob, age=25)]}

→ 결과: Map<Int, List>
→ db에서 인덱스 타더라도 groupBy/aggregate 때문에 slow가 발생하는경우가 왕왕 있다 → 메모리에 올려놔도 상관없는 경우에 app단에서 groupBy를 사용하는게 더 빠른 경우가 있다

5.2.4 flapMap, flatten

flatMap

인자로 주어진 람다를 컬렉션 모든 객체에 매핑

data class Book(val title: String, val author: List<String>) {
    companion object
}

fun main() {
    val books = listOf(
        Book("kotlin in action", listOf("rein", "moka", "leean")),
        Book("modern java in action", listOf("jace", "dante", "moka"))
    )

    println(books.flatMap { it.author }.toSet())
}

flatten

특별히 매핑이 필요 없는 경우

외에 더 많은 API는 https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-collection/#kotlin.collections.Collection 를 참고하자~!

→ 람다 안에 뭔가 로직이 복잡하다,,? 중첩된다? 싶으면 바로 검색

profile
Hongik CE

0개의 댓글