5. [1] 람다 식과 멤버 참조

  • 람다는 기본적으로 다른 함수에 넘길 수 있는 작은 코드 조각을 뜻한다.
  • 이를 사용하면 쉽게 공통 코드 구조를 라이브러리 함수로 뽑아내는 것이 가능하다.

(1) 람다 소개: 코드 블럭을 함수 인자로 넘기기

button.setOnClickListener(new OnClickListener) {
  @Override
  public void onClick(View view) {
    /* 버튼을 클릭 시 실행 할 동작 */
  }
}
button.setOnClickListener { /* 버튼을 클릭 시 실행 할 동작*/ }
  • 함수형 프로그래밍에서는 함수를 하나의 값 처럼 다루어서 번거로운 문제들을 해결한다.
  • 클래스를 선언하고 인서턴스를 함수에 넘기는 대신, 함수를 직접 다른 함수에 전달한다.
  • 람다 식을 사용하면, 함수를 선언할 필요가 없고, 코드 블록을 직접 함수의 인자로 전달하는 것이 가능하다.
  • 해당 예제들은 람다를 하나뿐인 무명 객체 대신 사용할 수 있다는 사실을 보여준다.

(2) 람다와 컬렉션

data class Person(val name: String, val age: Int)
fun findTheOldest(people: List<Person>) {
  var maxAge = 0                  // 가장 많은 나이 저장
  var theOldset: Person? = null   // 가장 연장자인 사람 저장
  for(person in people) {
    if(person.age > maxAge) {
      maxAge = person.age         // 현재까지 발견한 최연장자보다 더 나이가 많으면 변경
      theOldest = person
    }
  }
  println(theOldest)
}

val people = listOf(Person("Alice", 29), Person("Bob", 31))
findTheOldest(people)
// Person(name=Bob, age=31)
val people = listOf(Person("Alice", 29), Person("Bob", 31))
println(people.maxBy { it.age })    // 나이 프로퍼티를 비교하여 가장 값이 큰 원소 찾기
// Person(name=Bob, age=31)
people.maxBy(Person::age)
  • 코드에서 중복을 제거하는 것은 스타일 개선을 위한 중요한 방법 중 하나이다.
  • maxBy는 가장 큰 원소를 찾기 위해 비교에 사용할 값을 돌려주는 함수를 인자로 받는다.
  • { it.age }는 바로 비교에 사용할 값을 돌려주는 함수이다.

(3) 람다 식의 문법

val people = listOf((Person("이몽룡", 29), Person("성춘향", 31))
val names = people.joinToString(" ") { p: Person -> p.name }
println(names)    // 이몽룡 성춘향
  • 람다는 값처럼 여기저기 전달이 가능한 동작의 모음이다. 이는 따로 선언해서 변수에 저장하는 것도 가능하다.
  • 함수에 인자로 넘기면서 바로 람다를 정의하는 것이 대부분이다.
  • 코틀린 람다 식은 항상 중괄호로 쌓여 있으며, 화살표가 인자 목록과 람다 본문을 구분해준다.
  • 코드의 일부분을 블록으로 둘러싸서 실행할 필요가 있다면 run을 사용한다. 이는 인자로 받은 람다를 실행해준다.

(4) 현재 영역에 있는 변수에 접근

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 errors, $serverErrors server errors")
}
val responses = listOf("200 OK", "418 I'm a teapot", "500 Internal Server Error")
printProblemCounts(responses)   // 1 client errors, 1 server errors
fun tryToCountButtonClicks(button: Button): Int {
  var clicks = 0
  button.onClick { clicks++ }
  return clicks
}
  • forEach는 가장 기본적인 컬렉션 조작 함수 중 하나이다.
  • 이는 컬렉션의 모든 원소에 대해서 람다를 호출해준다.
  • Kotlin의 람다 안에서는 파이널 변수가 아닌 변수에 접근하는 것이 가능하다. 또한, 안에서 바깥의 변수를 변경해도 된다.
  • 해당 예제의 prefix, clientErrors, serverErrors처럼 람다 안에서 사용하는 외부 변수를 람다가 포획한 변수라 한다.
  • 어떤 함수가 자신의 로컬 변수를 포획한 람다를 반환하거나 다른 변수에 저장한다면 생명주기가 변경된다.
  • 람다를 이벤트 핸들러나 다른 비동기적으로 실행되는 코드로 활용하는 경우, 함수 호출이 끝난 후에 변수가 변경될 수도 있다.
  • 위 예제의 함수는 항상 0을 반환한다. onClick 핸들러가 호출될 때마다 clicks를 증가시키나, 변경을 관찰할 수 없다.
  • 핸들러는 함수가 clicks를 반환한 후 호출되는 이유이다.

(5) 멤버 참조

val getAge = { person: Person -> person.age }
Person::age   // 두 식은 같은 의미임
  • 코틀린에서는 멤버 참조를 사용하여 함수를 값으로 바꿀 수 있다.
  • 멤버 참조는 프로퍼티나 메서드를 단 하나만 호출하는 함수 값을 만들어준다.
  • 멤버 참조는 그 멤버를 호출하는 람다와 같은 타입이며, 최상위에 선언된 함수나 프로퍼티를 참조할 수 있다.