📘 Backend/Kotlin

Kotlin - Lambda

신건우 2023. 4. 29. 00:10

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

목차

  • Java에서 람다를 다루기 위한 노력
  • Kotlin에서의 Lambda
  • Closure
  • Try-With-Resources

Java에서 람다를 다루기 위한 노력

과일 가게에서 특정 과일만 요구하는 코드 예시

  • 만약 다른 종류의 과일을 같이 요구할때, 가격을 필터링 해야할때, 아래의 코드는 매우 비효율적인 코드이다.
  • 파라미터를 늘리는 것도 좋지만 람다를 이용하는게 훨씬 깔끔하고 좋다.
public calss Fruit {
    private final String name;
    private final int price;

    public Fruit(String name, int price) {
        this.name = name;
        this.price = price;
    }

    public String getName() { return name; }

    public int getPrice() {
        return price;
    }

    public static void main(String[] args) {

        List<Fruit> fruits = Arrays.asList(
        new Fruit("사과", 1_000),
        new Fruit("사과", 1_200),
        new Fruit("사과", 1_200),
        new Fruit("사과", 1_500),
        new Fruit("바나나", 3_000),
        new Fruit("바나나", 3_200),
        new Fruit("바나나", 2_500),
        new Fruit("수박", 10_000),
        );

        findFruits(fruits);
    }

    private List<Fruit> findFruits(List<Fruit> fruits, String name) {
        List<Fruit> apples = new ArrayList<>();

        for (Fruit fruit : fruits) {
            if (fruit.getName().equals(name)) {
                apples.add(fruit);
            }
        }
        return apples;
    }
}

개선된 코드

  • 인터페이스의 파라미터로 익명 클래스인 FruitFilter를 받고있다.
  • 함수를 호출할때 `filterFruits(fruits, new FruitFilter()) 익명 클래스를 넣어주면 된다.
  • 생성한 익명 클래스 내에서 인터페이스의 메서드를 Override 하여,
    Custom한 필터를 만들어 파라미터로 넘길 수 있다.
public interface FruitFilter {
    boolean isSelected(Fruit fruit);
}

// 익명 클래스
filterFruits(fruits, new FruitFilter()) {
    @Override
    public boolean isSelected(Fruit fruit) {
        return Arrays.asList
            ("사과", "바나나").contains(fruit.getName()) 
            &&
            fruits.getPrice() > 5000;
    }
}

private List<Fruit> filterFruits(List<Fruit> fruits, FruitFilter filter) {
    List<Fruit> results = new ArrayList<>();

    for (Fruit fruit : fruits) {
        if (filter.isSelected(fruit)) {
            results.add(fruit);
        }
    }

    return results;
}

람다 사용으로 인해 간결해진 코

filterFruits(fruits, fruit -> fruit.getName().equals("사과"));

Kotlin에서의 Lambda

  • 코틀린에서는 함수가 그 자체로 값이 될 수 있다.
  • 변수에 할당 할 수도, 파라미터로 넘길 수도 있다.
  • 마지막 파라미터가 함수인 경우, 소괄호밖에 람를 사용 가능하다.

fun main() {
    val fruits = listOf(
        new Fruit("사과", 1_000),
        new Fruit("사과", 1_200),
        new Fruit("사과", 1_200),
        new Fruit("사과", 1_500),
        new Fruit("바나나", 3_000),
        new Fruit("바나나", 3_200),
        new Fruit("바나나", 2_500),
        new Fruit("수박", 10_000),
        )

    // 람다를 만드는 방법 1 - 함수 명이 없다
    val isApple: (Fruit) -> Boolean = fun(fruit: Fruit) {
        return fruit.name == "사과"
    }

    // 람다를 만드는 방법 2 - 함수 명이 없다
    val isApple2: (Fruit) -> Boolean = { fruit: Fruit -> fruit.name == "사과"}

    // 함수 호출
    isApple(fruits[0])
    // 명시적 함수 호출
    isApple.invoke(fruits[0])

    // isApple 함수를 넣어 호출
    filterFruits(fuirts, isApple)

    // isApple2 함수를 넣어 호출
    filterFruits(fruits) { it.name == "사과" }
}

// filter 함수 자체를 파라미터로 받아 사용
private fun filterFruits(fruits: List<Fruit>, filter: (Fruit) -> Boolean): List<Fruit> {
    val results = mutableListOf<Fruit>()

    for (fruit in fruits) {
        if (filter(fruit)) {
            results.add(fruit)
        }
    }
    return results
}

Closure

람다 실행 시점에 참조하고 있는 변수들을 모두 가져와 사용 가능하게 만들어 준다.

람다를 일급 시민으로 간주할 수 있게 해준다.


*자바 코드 *

  • 자바는 람다를 쓸 때 사용할 수 있는 변수에 제약이 있다.
  • 확실한 final 변수가 아니면 사용이 불가능하다.
String targetFruitName = "바나나";
targetFruitName = "수박";

// 컴파일 에러
filterFruits(fruits, (fruit) -> targetFruitName.equals(fruit.getName()));

코틀린 코드

  • 코틀린에서는 아무런 문제 없이 동작한다.
  • 람다가 시작하는 지점에 참조하고 있는 변수들을 모두 포획하여 그 정보를 가지고 있다.
var targetFruitName = "바나나"
targetFruitName = "수박"
filterFruits(fruits) { it.name = targetFruitName }

Try-With-Resources

T.use부분은 Closeable 구현한 구현체인 T에 대한 확장함수이며, 파라미터로 람다를 받는다.

파라미터는 block이라는 이름을 가진 함수이며 이 block 함수는 T 타입을 받아서 R 타입을 반환한다.

즉, 람다를 받도록 만들어진 함수가 block 함수이다.

public inline fun <T: Closeable?, R> T.use(block: (T) -> R): R {}

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

정리

함수는 Java에서 2급시민이지만, 코틀린에서는 1급시민이다.

때문에, 함수 자체를 변수에 넣을 수도 있고 파라미터로 전달할 수도 있다.

코틀린에서 함수 타입은 (파라미터 타입, ...) -> 반환타입 이었다.


코틀린에서 람다는 두 가지 방법으로 만들 수 있고, { } 방법이 더 많이 사용된다.

함수를 호출하며, 마지막 파라미터인 람다를 쓸 때는 소괄호 밖으로 람다를 뺄 수 있다


코틀린에서 람다는 두 가지 방법으로 만들 수 있고, { } 방법이 더 많이 사용된다.

함수를 호출하며, 마지막 파라미터인 람다를 쓸 때는 소괄호 밖으로 람다를 뺄 수 있다.

람다의 마지막 expression 결과는 람다의 반환 값이다.


코틀린에서 람다는 두 가지 방법으로 만들 수 있고, { } 방법이 더 많이 사용된다.

함수를 호출하며, 마지막 파라미터인 람다를 쓸 때는 소괄호 밖으로 람다를 뺄 수 있다.

람다의 마지막 expression 결과는 람다의 반환 값이다.

코틀린에서는 Closure를 사용하여 non-final 변수도 람다에서 사용 할 수 있다