Languages

kotlin에서의 람다식과 멤버 참조 (lambda, ::, 값으로 표현된 함수)

!쪼렙조햄 2020. 4. 26. 21:27
반응형

kotlin에서의 람다식과 멤버 참조 (lambda, ::, 값으로 표현된 함수)

  • 람다
    • 다른 함수에 넘길 수 있는 작은 코드 조각
    • 쉽게 공통 코드 구조를 라이브러리 함수로 뽑아낼 수 있다
  • 람다 주 사용처
    • 컬렉션 처리
  • 람다 식은 java 에도 있다 (버전 8부터)

😎 람다 소개 : 코드 블록을 함수 인자로 넘기기

  • 람다 이전의 방식
    • 무명 내부 클래스 사용 (java)
// 이름이 없는 클래스 (watchTV) 예시
abstract class TV {
    public abstract void powerOn();
}

public class Test{
    public static void watchTV(TV tv){
        tv.powerOn();
    }
    public static void main(String[] args){
        watchTV(new TV(){
            public void powerOn(){
                System.out.println("Power On");
            }
        });
    }
}
  • 무명 내부 클래스
    • 클래스 안에서 구현 된 클래스
    • TV 클래스를 상속한 내부 클래스가 만들어지게 된다
    • 이름을 갖는 클래스의 경우에 class NamedClass extends TV 이렇게 구현
    • 무명내부 클래스를 사용하면 코드를 함수에 넘기거나 변수에 저장할 수 있다
  • 함수를 값처럼 다루는 방식
    • 함수형 프로그래밍에서 사용
    • 함수를 직접 다른 함수에 전달하는 방식

💩 kotlin collection을 lambda 식으로 다루기

val people = listOf(Person("Alice", 29), Person("Bob", 31))
println(people.maxBy{it.age})
println(people.maxBy(Person::age))
  • kotlin에서는 모든 컬렉션에 대해 maxBy 함수를 호출할 수 있다
  • maxBy 함수의 인자
    • 가장 큰 원소를 찾을때 비교에 사용할 값을 인자로 받는다
  • Person::age
    • 멤버 참조를 사용하여 컬렉션 검색하기

🤖 람다 식의 문법

  • 람다식은 항상 중괄호 사이에 위치한다
  • ->
    • 인자 목록과 람다 본문을 구분해 준다
    • 오른쪽 : 람다 본문
      val sum = {x: Int, y: Int -> x+y}
      println(sum(1,2))
  • 람다식을 sum이라는 변수에 저장하여 함수처럼 사용가능
  • { } 중괄호에 묶인 것은 람다식 (일반 함수로 사용가능) 이 되어 바로 호출도 가능하다
    • {println(2)}()
  • run을 사용해서 람다식을 실행할 수 있다
    • run {println(2)}
      people.maxBy{p : Person -> p.age}
  • 정식으로 람다식 사용했을때의 모습
  • 위 코드의 문제점
    • 구분자가 많아 낮은 가독성
    • 컴파일러가 문맥으로부터 유추할 수 있는 인자타입 명시
  • 코틀린에서는 함수 호출 시, 맨 마지막 인자가 람다식이면 그 람다를 괄호 밖으로 뺄 수 있다
    • `people.maxBy(){p:Person -> p.age}
  • 그 함수의 유일한 인자가 람다식이라면 괄호도 생략 가능하다
    • `people.maxBy{p:Person -> p.age}
    • 이렇게 사용을 할때, 마지막 인자가 어떤 인자인지 이름도 생략되기 때문에 함수에 익숙하지 않은 사람에게는 오히려 더 어려울 수 있다
  • intelliJ 꿀팁
    • 람다식을 괄호 밖으로 이동하기 / 람다식을 괄호 안으로 이동하기 두가지 옵션이 있다
  • 컴파일러는 람다 파라미터의 타입도 추론할 수 있다
    • people.maxBy{p -> p.age}
    • 컴파일러가 람다 파라미터의 타입을 추론하지 못하는 경우도 있을 수 있다
      • 람다를 변수에 저장할 때는 파라미터의 타입을 추론할 문맥이 존재하지 않는다
      • val getAge = {p -> p.age}
        • 위 코드 에러
        • p: Person 명시해야 한다
    • 여러 파라미터중에 몇개만 타입을 명시하는것도 가능
  • it 으로 파라미터 표현
    • 파라미터가 하나뿐일때만 사용 가능한가?
    • people.maxBy{it.age}
    • 람다 안에 람다가 중첩되는 경우 각 람다의 파라미터를 명시해줄 것
  • 본문이 여러 줄로 이루어진 람다식
    • 본문의 맨 마지막에 있는 식이 람다의 결과값

🥴 kotlin lambda 의 변수 capture

  • java의 무명 내부 클래스
    • 메소드의 로컬 변수를 무명내부 클래스에서 사용할 수 있었다
  • 람다를 함수 안에서 정의한 경우에 함수의 파라미터 뿐만 아니라 람다 정의의 앞에 선언된 로컬 변수까지 사용 가능
    fun printMessageWithPrefix(messages : Collection<String>, prefix : String){
      messages.forEach{
          println("$prefix $it")
      }
    }
    val errors = listOf("403 Forbidden", "404 Not Found")
    printMessageWithPrefix(errors, "ERROR : ")
  • List와 Set은 Collection을 상속한다
  • List, Set과 같은 클래스들은 Collection에 대입할 수 있기 때문에 messages 저런식으로 사용가능
  • kotlin에서는 자바와 다르게 람다 안에서 파이널 변수가 아닌 변수에 접근할 수 있다?
    • java에서는 static 변수에만 접근할 수 있었나?
    • 람다 안에서 바깥의 변수를 변경해도 된다
  • 함수 내부 로컬 변수의 생명주기
    • 보통은 함수가 반환되면 해당 변수의 생명주기도 끝난다
    • kotlin에서 함수가 로컬변수를 포획한 람다를 반환하거나 다른 변수에 저장하는 경우 생명주기가 달라질 수 있다
      fun tryToCountButtonClicks(button : Button):Int{
      var clicks = 0
      button.onClick {clicks++}
      return clicks
      }
  • 람다를 이벤트 핸들러나 다른 비동기적으로 실행되는 코드로 활용시 유의
  • 함수 호출이 끝난 다음에 로컬 변수가 변경될 수도 있다
  • 위 함수는 항상 0을 반환한다
    • 해결방법
      • clicks 변수를 함수 외부로 빼야 한다

😧 멤버 참조 ::

  • 함수를 값으로 바꾸기
    • val getAge = Person::age
  • ::
    • 멤버참조
    • 프로퍼티나 메소드를 단 하나만 호출하는 함수 값을 만들어준다
    • 함수, 프로퍼티 상관없이 :: 뒤에 괄호를 넣으면 안된다
      fun salute() = println("SALUTE")
      run (::salute)
  • 최상위 함수를 참조
  • run은 인자로 받은 함수를 호출한다
    val action = {person:Person, message:String -> sendEmail(person, message)}
    val nextAction = ::sendEmail
  • person, message 두개의 인자를 가진 람다가 sendEmail 함수에 작업을 위임한다
  • ::sendEmail
    • 람다 대신 멤버 참조를 쓸 수 있다
    • 그러면 최상위 멤버 참조이니 같은 파일에 person, message가 있는것인가?
  • 확장함수도 멤버함수와 같은 방식으로 참조 가능

Reference

반응형