Kotlin in Action : 람다 프로그래밍

한포도·4일 전
0

About Kotlin

목록 보기
5/6
post-thumbnail

들어가며

프로그래밍을 하다 보면 "이 코드 블록을 나중에 실행하고 싶다" 또는 "이 데이터로 이런 처리를 해주세요"라는 요구사항을 자주 마주치게 됩니다. 전통적인 방식으로는 이런 요구사항을 구현하기 위해 익명 클래스를 사용했지만, 코드가 번잡해지고 가독성이 떨어지는 문제가 있었습니다. 코틀린의 람다식은 이러한 문제를 해결해줍니다.

1. 람다식 이해하기

람다식이란?

람다식은 다른 함수에 넘길 수 있는 작은 코드 조각입니다. 쉽게 말해 "이름 없는 함수"라고 생각하면 됩니다.

기본 문법

코틀린의 람다식은 항상 중괄호({})로 둘러싸여 있으며, 다음과 같은 구조를 가집니다:

{ 파라미터1, 파라미터2 -> 본문 }

예를 들어 두 숫자를 더하는 람다식은 이렇게 작성합니다:

val sum = { x: Int, y: Int -> x + y }

이것은 다음 일반 함수와 동일한 역할을 합니다:

fun sum(x: Int, y: Int): Int {
    return x + y
}

람다식 호출하기

람다식을 변수에 저장했다면, 일반 함수처럼 호출할 수 있습니다:

val sum = { x: Int, y: Int -> x + y }
println(sum(1, 2)) // 출력: 3

// 람다식 직접 실행도 가능합니다
println({ x: Int, y: Int -> x + y }(1, 2)) // 출력: 3

2. 컬렉션에서의 람다 활용

filter와 map: 기본적이지만 강력한 도구

컬렉션을 다룰 때 가장 많이 사용되는 연산이 바로 filter와 map입니다.

filter: 조건에 맞는 원소 걸러내기

val numbers = listOf(1, 2, 3, 4, 5, 6)
val evenNumbers = numbers.filter { it % 2 == 0 }
println(evenNumbers) // 출력: [2, 4, 6]

여기서 it은 무엇일까요? 람다식의 파라미터가 하나일 때 코틀린은 자동으로 이 파라미터를 it이라는 이름으로 사용할 수 있게 해줍니다.

map: 원소 변환하기

val numbers = listOf(1, 2, 3, 4, 5)
val squared = numbers.map { it * it }
println(squared) // 출력: [1, 4, 9, 16, 25]

실전 예제: 사람 데이터 처리하기

실제 업무에서는 이런 단순한 숫자가 아닌 복잡한 데이터를 다루게 됩니다. 다음 예제를 봅시다:

data class Person(
    val name: String,
    val age: Int,
    val salary: Double
)

val people = listOf(
    Person("Alice", 29, 50000.0),
    Person("Bob", 31, 55000.0),
    Person("Charlie", 25, 45000.0),
    Person("Diana", 35, 60000.0)
)

// 30세 이상인 사람들의 이름만 추출
val over30Names = people
    .filter { it.age >= 30 }
    .map { it.name }
println(over30Names) // 출력: [Bob, Diana]

// 평균 급여 계산
val averageSalary = people
    .map { it.salary }
    .average()
println(averageSalary) // 출력: 52500.0

유용한 컬렉션 연산들

all: 모든 원소가 조건을 만족하는지 검사

val allAdults = people.all { it.age >= 18 }
println(allAdults) // 출력: true

any: 조건을 만족하는 원소가 하나라도 있는지 검사

val hasRichPeople = people.any { it.salary > 55000.0 }
println(hasRichPeople) // 출력: true

count: 조건을 만족하는 원소의 개수 세기

val over30Count = people.count { it.age >= 30 }
println(over30Count) // 출력: 2

find: 조건을 만족하는 첫 번째 원소 찾기

val firstOver30 = people.find { it.age >= 30 }
println(firstOver30?.name) // 출력: Bob

3. 성능 최적화: 시퀀스 활용하기

여러 개의 컬렉션 연산을 체이닝할 때는 중간 결과를 저장하는 임시 컬렉션이 생성됩니다. 이는 성능 저하의 원인이 될 수 있습니다.

// 이 코드는 중간에 두 개의 리스트를 추가로 생성합니다
people.map { it.name }       // 첫 번째 중간 리스트
    .filter { it.length > 4 } // 두 번째 중간 리스트
    .take(2)                 // 최종 결과

시퀀스를 사용하면 이런 중간 컬렉션 생성을 피할 수 있습니다:

// 시퀀스를 사용하면 중간 컬렉션이 생성되지 않습니다
people.asSequence()
    .map { it.name }
    .filter { it.length > 4 }
    .take(2)
    .toList()

시퀀스의 동작 방식

시퀀스는 각 원소에 대해 모든 연산을 한 번에 처리합니다:

people.asSequence()
    .map {
        println("map: ${it.name}")
        it.name
    }
    .filter {
        println("filter: $it")
        it.length > 4
    }
    .take(2)
    .toList()

이 코드의 실행 순서를 보면:

  1. 첫 번째 원소에 대해 map 실행
  2. 같은 원소에 대해 filter 실행
  3. 결과가 조건을 만족하면 결과 리스트에 추가
  4. 다음 원소로 이동

4. 수신 객체 지정 람다: with와 apply

with 함수

객체의 메서드나 프로퍼티를 연속해서 호출할 때 객체 이름을 반복하지 않고 깔끔하게 처리할 수 있습니다.

// with 사용 전
val sb = StringBuilder()
sb.append("Hello")
sb.append(" ")
sb.append("World")
println(sb.toString())

// with 사용 후
val result = with(StringBuilder()) {
    append("Hello")
    append(" ")
    append("World")
    toString()
}
println(result)

apply 함수

객체를 생성하면서 초기화할 때 매우 유용합니다. apply는 수신 객체를 다시 반환한다는 점이 with와 다릅니다.

val person = Person("John", 0, 0.0).apply {
    // this는 Person 객체를 가리킵니다
    age = 25
    salary = 50000.0
}

특히 안드로이드 개발에서 뷰를 초기화할 때 자주 사용됩니다:

val textView = TextView(context).apply {
    text = "Hello, World!"
    textSize = 20f
    textColor = Color.BLACK
    setPadding(16, 16, 16, 16)
}

5. 자바와의 상호운용

코틀린의 람다는 자바의 함수형 인터페이스(SAM 인터페이스)와 완벽하게 호환됩니다.

// 자바 스타일
button.setOnClickListener(new View.OnClickListener {
    @Override
    public void onClick(View v) {
        // 처리 로직
    }
})

// 코틀린 람다 스타일
button.setOnClickListener { view ->
    // 처리 로직
}

람다 활용 실전 팁

1. 가독성을 위한 파라미터 명명

// it 사용
people.maxBy { it.age }

// 명시적 파라미터 사용
people.maxBy { person -> person.age }

2. 디버깅을 위한 로그 추가

people.map { person ->
    println("Processing: ${person.name}")
    person.name.uppercase()
}

3. 복잡한 로직의 분리

너무 긴 람다는 별도의 함수로 분리하는 것이 좋습니다:

// 람다가 길어지는 경우
people.filter { person ->
    val isAdult = person.age >= 18
    val hasGoodSalary = person.salary >= 50000
    val isExperienced = person.yearsOfExperience >= 5
    isAdult && hasGoodSalary && isExperienced
}

// 함수로 분리
fun isEligibleEmployee(person: Person): Boolean {
    val isAdult = person.age >= 18
    val hasGoodSalary = person.salary >= 50000
    val isExperienced = person.yearsOfExperience >= 5
    return isAdult && hasGoodSalary && isExperienced
}

people.filter(::isEligibleEmployee)

마치며

코틀린의 람다는 단순한 문법적 함수가 아닌, 강력한 프로그래밍 도구입니다. 람다를 잘 활용하면:

  • 더 간결하고 표현력 있는 코드 작성 가능
  • 컬렉션 처리의 효율성과 가독성 향상
  • 함수형 프로그래밍의 장점 활용
  • 확장 가능하고 유지보수하기 쉬운 코드 작성
profile
응애 개발맨
post-custom-banner

0개의 댓글