Lined Notebook

[자바 개발자를 위한 코틀린 입문] 람다 다루기

by dev.hardy

코틀린에서 람다를 다루는 방법

인프런에서 최태현 지식 공유자님의
자바 개발자를 위한 코틀린 입문을 수강하며 정리한 내용 입니다.
하단에 강의 링크 있습니다.

자바에서 람다를 다루기 위한 노력

복잡한 조건에 따라 자꾸 메소드와 파라미터 늘리는 것만으로는 어려워지게 됨.

→ 인터페이스와 익명클래스를 사용하자!

→ 이렇게 해서 많은 것이 해결되었지만 아쉬운점이 있다.

익명 클래스 를 사용하는 것은 복잡하다.

이러한 어려움을 해결하기 이해 JDK 8부터 람다 (이름이 없는 함수) 등장!

Predicate, Consumer, Function 등 여러 인터페이스를 JDK 8에서 만들어 두었음.

그리고 간결한 스트림이 등장했다. (병럴처리에도 강점이 생김)

메소드 레퍼런스: 메소드를 넘기는 것 처럼사용할 수 있다.

→ 메소드를 넘기는 것처럼의 이유 : Java에서 함수는 변수에 할당되거나 파라미터로 전달할 수 없다. (2급 시민)

→ 그러면? 메소드를 넘기는 것이 아니라 Predicate와 같은 함수형 인터페이스로 인해 가능한 것

코틀린에서의 람다

자바와는 근본적으로 다른 한 가지가 있다.

  • 코틀린에서는 함수가 그 자체로 값이 될 수 있다.
  • 변수에 할당할 수 있다.
  • 파라미터로 넘길 수 있다.

예) 사과인지를 체크하는 함수를 변수에 할당하는 두 가지 방법 (람다를 만드는 방법) (1), (2)

fun main() {
    val fruits = listOf(
        Fruit("사과", 1000),
        Fruit("사과", 1200),
        Fruit("사과", 1200),
        Fruit("사과", 1500),
        Fruit("바나나", 3000),
        Fruit("바나나", 3200),
        Fruit("바나나", 2500),
        Fruit("수박", 10000),
    )

    val isApple = fun(fruit: Fruit): Boolean { // (1)
        return fruit.name == "사과"
    }

    val isApple2 = { fruit: Fruit -> fruit.name == "사과"} // (2)
}

(1) 일반적인 함수를 만드는 방법인데 함수이름 을 표시하지 않는다.

(2) 중괄호와 화살표를 사용하는 방법

예) 부르는 방법

isApple(fruits[0])
isApple.invoke(fruits[0])

그리고 함수를 할당안 변수에도 타입이 있다.

 val isApple: (Fruit) -> Boolean = fun(fruit: Fruit): Boolean { 
        return fruit.name == "사과"
 }

타입은 (Fruit) -> Boolean 이며, 파라미터로 Fruit을 받아 Boolean을 리턴하는 함수라는 뜻이 된다.

즉, 함수의 타입: (파라미터 타입…) → 반환타입

private fun filterFruits(
    fruits: List<Fruit>,
    filter: (Fruit) -> Boolean // (1)
): List<Fruit> {
    val results = mutableListOf<Fruit>()
    for (fruit in fruits) {
        if (filter(fruit)) {
            results.add(fruit)
        }
    }
    return results
}

(1) 코틀린에서는 함수를 파라미터로 받을 수 있기때문에 파라미터: 함수 타입 으로 파라미터를 명시해서 함수를 받을 수 있다.

예) 사용해보자

fun main() {
    val fruits = listOf(
        Fruit("사과", 1000),
        Fruit("사과", 1200),
        Fruit("사과", 1200),
        Fruit("사과", 1500),
        Fruit("바나나", 3000),
        Fruit("바나나", 3200),
        Fruit("바나나", 2500),
        Fruit("수박", 10000),
    )

    val isApple = {fruit: Fruit -> fruit.name == "사과"} // (1)

    val results = filterFruits(fruits, isApple) // (2)

    results.forEach { 
        println(it.name) // 사과만 나온다.
    }
}

(1) (Fruit) → Boolean 함수타입인 함수를 만들어 준다.

(2) 만들어준 isApple이라는 함수를 파라미터로 넘긴다.

→ 익명함수이기 때문에 바로 함수를 변수에 할당하지 않고 그대로 넣어줄 수도 있다.

🤠 마지막 파라미터가 함수인 경우, 소괄호 밖에 람다 사용 가능

😸 람다를 작성할때, 람다의 파라미터를 it으로 직접 참조 가능

🤠 람다는 여러줄 작성 가능한데, return을 명시하지 않더라도 마지막 줄의 결과가 람다의 반환값이다!

filterFruits(fruits) {it.name == "사과"}

Closure

자바에서 람다는 외부 변수를 사용할 때 final인 변수나 실질적으로 final인 변수만 사용가능 했는데

코틀린에서는 아무 문제없이 사용할 수 있다.

var targetName = "사과"
targetName = "수박"
filterFruits(fruits) {it.name == targetName}

🧱 이것이 어떻게 가능할까?

→ 코틀린에서는 람다가 시작하는 지점에 참조하고 있는 변수들을 모두 포획 하여 그 정보를 가지고 있다.

이렇게 해야만, 람다를 진정한 일급 시민으로 간주할 수 있으며 이 데이터 구조를 Closure 라고 부른다.

**Closure**: 람다가 실행되는 시점에 쓰고 있는 변수들을 모두 포획한 데이터 구조

다시 try with resources

fun readFile(path: String) {
        BufferedReader(FileReader(path)).use { reader -> // (1)
            println(reader.readLine())
        }
    }

(1) use를 주목

public inline fun <T : Closeable?, R> T.use(block: (T) -> R): R {
  1. Closeable 구현체에 대한 확장함수이다.
  2. inline 함수이다.
  3. 람다를 받게 만들어진 함수이다.

Reference:
자바 개발자를 위한 코틀린 입문

블로그의 정보

개발자 하디 : 기록저장소

dev.hardy

활동하기