Kim-Baek 개발자 이야기

Kotlin 함수형 프로그래밍 완벽 가이드 | Lambda, Collection, Scope Function으로 코드 간결성 10배 높이기 🚀 본문

개발/java basic

Kotlin 함수형 프로그래밍 완벽 가이드 | Lambda, Collection, Scope Function으로 코드 간결성 10배 높이기 🚀

김백개발자 2025. 12. 2. 23:47
반응형

이 글을 읽으면: Kotlin의 핵심인 함수형 프로그래밍을 마스터할 수 있습니다. Lambda, Higher-order Functions, map/filter/groupBy 등의 Collection 연산, 그리고 let/apply/run 같은 Scope Functions를 실전 예제로 배워보세요. Java의 Stream API보다 훨씬 강력합니다!


📌 목차

  1. 함수형 프로그래밍이란? - 왜 배워야 할까
  2. Lambda & Higher-order Functions
  3. Collection Operations 완전 정복
  4. Scope Functions (let, apply, run, with, also)
  5. 실전 패턴 모음
  6. 성능 최적화 팁

1. 함수형 프로그래밍이란? - 왜 배워야 할까

😫 명령형 프로그래밍의 불편함 (Java 스타일)

// Java - 명령형 프로그래밍
List<User> activeUsers = new ArrayList<>();
for (User user : users) {
    if (user.isActive()) {
        activeUsers.add(user);
    }
}

List<String> names = new ArrayList<>();
for (User user : activeUsers) {
    names.add(user.getName());
}

Collections.sort(names);

문제점:

  • 코드가 길다 (15줄)
  • 중간 변수 필요 (activeUsers, names)
  • 의도 파악이 어렵다
  • 실수하기 쉽다

✨ 함수형 프로그래밍 (Kotlin 스타일)

// Kotlin - 함수형 프로그래밍
val names = users
    .filter { it.isActive }
    .map { it.name }
    .sorted()

장점:

  • 코드가 간결하다 (4줄)
  • 중간 변수 불필요
  • 의도가 명확하다 ("활성 사용자의 이름을 정렬")
  • 실수할 여지가 적다

2. Lambda & Higher-order Functions

2.1 Lambda 기본 개념

Lambda는 이름 없는 함수입니다.

// 일반 함수
fun double(x: Int): Int {
    return x * 2
}

// Lambda 표현식
val double = { x: Int -> x * 2 }

// 사용
println(double(5))  // 10

2.2 Lambda 문법 완전 정복

// 1. 기본 형태
{ x: Int, y: Int -> x + y }

// 2. 파라미터 타입 생략 (타입 추론)
{ x, y -> x + y }

// 3. 파라미터가 하나일 때 'it' 사용
{ it * 2 }

// 4. 파라미터 없을 때
{ println("Hello") }

// 5. 여러 줄 Lambda
{ x: Int ->
    val doubled = x * 2
    val squared = doubled * doubled
    squared  // 마지막 표현식이 반환값
}

2.3 실전 예제 - joinToString

// 멤버 리스트를 Slack 멘션 형식으로 변환
val members = listOf("john", "jane", "bob")

// Lambda 활용
val mentions = members.joinToString(" ") { "@$it" }
println(mentions)  // "@john @jane @bob"

// 더 복잡한 예제
data class User(val name: String, val age: Int)
val users = listOf(
    User("김철수", 30),
    User("이영희", 25),
    User("박민수", 35)
)

val nameList = users.joinToString(", ") { "${it.name}(${it.age}세)" }
println(nameList)  // "김철수(30세), 이영희(25세), 박민수(35세)"

2.4 Higher-order Functions

Higher-order Function: 함수를 파라미터로 받거나 반환하는 함수

// 함수를 파라미터로 받기
fun processNumbers(numbers: List<Int>, operation: (Int) -> Int): List<Int> {
    return numbers.map { operation(it) }
}

// 사용
val numbers = listOf(1, 2, 3, 4, 5)
val doubled = processNumbers(numbers) { it * 2 }
println(doubled)  // [2, 4, 6, 8, 10]

val squared = processNumbers(numbers) { it * it }
println(squared)  // [1, 4, 9, 16, 25]

2.5 실무 예제 - 조건부 필터링

// 실무 코드: 조건에 따라 필터링을 적용하거나 하지 않음
fun <T> Iterable<T>.filterIf(
    condition: Boolean, 
    predicate: (T) -> Boolean
): Iterable<T> {
    return if (condition) this.filter(predicate) else this
}

// 사용 예시
data class ServiceCode(val code: String, val value: String)

val serviceCodes = listOf(
    ServiceCode("planner", "기획자"),
    ServiceCode("dev", "개발자"),
    ServiceCode("designer", "디자이너")
)

val catalogType = CatalogType.TECH

val filteredCodes = serviceCodes
    .filterIf(catalogType == CatalogType.TECH) { 
        !it.code.startsWith("planner") 
    }
// TECH 타입이면 planner 제외, 아니면 전체 반환

3. Collection Operations 완전 정복

3.1 map - 변환

용도: 각 요소를 다른 형태로 변환

// 기본 사용
val numbers = listOf(1, 2, 3, 4, 5)
val doubled = numbers.map { it * 2 }
println(doubled)  // [2, 4, 6, 8, 10]

// 객체 변환
data class User(val id: Long, val name: String)
data class UserResponse(val userId: Long, val userName: String)

val users = listOf(
    User(1, "김철수"),
    User(2, "이영희")
)

val responses = users.map { 
    UserResponse(
        userId = it.id,
        userName = it.name
    )
}

3.2 실전 예제 - 서비스 요약 생성

// 실무 코드: Entity를 Response DTO로 변환
data class ItgService(
    val itgServiceId: String,
    val name: String,
    val description: String
)

data class ItgServiceSummaryResponse(
    val id: String,
    val name: String,
    val managers: List<String>
) {
    companion object {
        fun from(
            service: ItgService, 
            managers: List<String>
        ) = ItgServiceSummaryResponse(
            id = service.itgServiceId,
            name = service.name,
            managers = managers
        )
    }
}

// 사용
val itgServices: List<ItgService> = getAllServices()
val serviceManagersMap: Map<String, List<String>> = getManagersMap()

val summaries = itgServices.map { service ->
    ItgServiceSummaryResponse.from(
        service, 
        serviceManagersMap[service.itgServiceId]!!
    )
}

3.3 filter - 필터링

용도: 조건에 맞는 요소만 선택

// 기본 사용
val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
val evenNumbers = numbers.filter { it % 2 == 0 }
println(evenNumbers)  // [2, 4, 6, 8, 10]

// 복잡한 조건
data class User(
    val name: String, 
    val age: Int, 
    val isActive: Boolean
)

val users = listOf(
    User("김철수", 30, true),
    User("이영희", 17, true),
    User("박민수", 35, false)
)

val activeAdults = users.filter { 
    it.isActive && it.age >= 18 
}
println(activeAdults)  // [User(김철수, 30, true)]

3.4 groupBy - 그룹화

용도: 특정 키를 기준으로 그룹화

// 기본 사용
data class Student(val name: String, val grade: Int)

val students = listOf(
    Student("김철수", 1),
    Student("이영희", 2),
    Student("박민수", 1),
    Student("최지훈", 2)
)

val byGrade = students.groupBy { it.grade }
println(byGrade)
// {1=[Student(김철수, 1), Student(박민수, 1)], 
//  2=[Student(이영희, 2), Student(최지훈, 2)]}

3.5 실전 예제 - 권한 매핑

// 실무 코드: 권한 그룹 매핑
data class AuthGroupMapping(
    val targetId: String,
    val userIds: List<String>
)

val authGroupMappings = listOf(
    AuthGroupMapping("SERVICE-001", listOf("user1", "user2")),
    AuthGroupMapping("SERVICE-002", listOf("user3")),
    AuthGroupMapping("SERVICE-001", listOf("user4"))
)

// targetId별로 그룹화
val groupedByTarget = authGroupMappings.groupBy { it.targetId }

// 결과:
// {
//   "SERVICE-001": [
//     AuthGroupMapping("SERVICE-001", ["user1", "user2"]),
//     AuthGroupMapping("SERVICE-001", ["user4"])
//   ],
//   "SERVICE-002": [
//     AuthGroupMapping("SERVICE-002", ["user3"])
//   ]
// }

3.6 associate - Map 생성

용도: Collection을 Map으로 변환

// 기본 사용
val users = listOf(
    User(1, "김철수"),
    User(2, "이영희"),
    User(3, "박민수")
)

// id를 key로 하는 Map 생성
val userMap = users.associateBy { it.id }
println(userMap)
// {1=User(1, 김철수), 2=User(2, 이영희), 3=User(3, 박민수)}

// key-value를 직접 지정
val nameToAge = users.associate { it.name to it.age }
println(nameToAge)
// {김철수=30, 이영희=25, 박민수=35}

3.7 실전 예제 - 서비스 매니저 매핑

// 실무 코드: 서비스별 매니저 매핑 생성
data class ServiceManager(
    val serviceId: String,
    val managerIds: List<String>
)

val serviceManagers = listOf(
    Pair("SERVICE-001", ServiceManager("SERVICE-001", listOf("m1", "m2"))),
    Pair("SERVICE-002", ServiceManager("SERVICE-002", listOf("m3")))
)

// Pair를 Map으로 변환
val managerMap = serviceManagers.associate { 
    it.first to it.second.managerIds 
}
println(managerMap)
// {SERVICE-001=[m1, m2], SERVICE-002=[m3]}

3.8 mapNotNull - Null 제거 + 변환

용도: 변환 후 null 제거

// 기본 사용
val numbers = listOf("1", "2", "three", "4", "five")
val validNumbers = numbers.mapNotNull { it.toIntOrNull() }
println(validNumbers)  // [1, 2, 4]

// 실전 예제
data class User(val id: Long, val email: String?)

val users = listOf(
    User(1, "user1@example.com"),
    User(2, null),
    User(3, "user3@example.com")
)

val validEmails = users.mapNotNull { it.email }
println(validEmails)  // [user1@example.com, user3@example.com]

3.9 실무 종합 예제 - Map Value Null 제거

// 실무 확장 함수: Map의 null value 제거
fun <T, U> Map<T, U?>.filterValueNotNull(): Map<T, U> {
    return this.mapNotNull { (key, value) -> 
        value?.let { key to it } 
    }.toMap()
}

// 사용 예시
val serviceMap = mapOf(
    "SERVICE-001" to "Active",
    "SERVICE-002" to null,
    "SERVICE-003" to "Inactive"
)

val cleanedMap = serviceMap.filterValueNotNull()
println(cleanedMap)  // {SERVICE-001=Active, SERVICE-003=Inactive}

4. Scope Functions

4.1 Scope Functions 개요

Function 객체 참조 반환값 주요 용도

let it Lambda 결과 null 체크 + 변환
apply this 객체 자신 객체 초기화
run this Lambda 결과 객체 설정 + 계산
with this Lambda 결과 여러 메서드 호출
also it 객체 자신 부가 작업

4.2 let - Null 체크 & 변환

용도: Nullable 객체를 안전하게 처리

// 기본 사용
val name: String? = "김철수"
name?.let { 
    println("이름: $it")
    println("길이: ${it.length}")
}

// 변환 + 반환
val length: Int? = name?.let { it.length }

4.3 실전 예제 - 조건부 데이터 조회

// 실무 코드: 조건부 자식 서비스 ID 조회
val childServiceIds = parentId?.let { 
    itgServiceCatalogService.getChildrenOf(parentId).map { it.id }
}
// parentId가 null이 아닐 때만 자식 조회 실행

동작 흐름:

  1. parentId가 null → childServiceIds도 null
  2. parentId가 "PARENT-001" → 자식 조회 실행 → ID 리스트 반환
// 더 복잡한 예제
fun getUserRecommendations(userId: Long?): List<Product> {
    return userId?.let { id ->
        val user = userRepository.findById(id) ?: return emptyList()
        val preferences = preferenceService.getPreferences(user)
        recommendationService.getRecommendations(preferences)
    } ?: emptyList()
}

4.4 copy - 데이터 클래스 수정

용도: 일부 속성만 변경한 새 인스턴스 생성

data class User(
    val id: Long,
    val name: String,
    val email: String,
    val age: Int
)

val user = User(1, "김철수", "kim@example.com", 30)

// 이름만 변경
val updated = user.copy(name = "김영철")
println(updated)  // User(1, 김영철, kim@example.com, 30)

// 여러 속성 변경
val updated2 = user.copy(
    name = "김영철",
    age = 31
)

4.5 실전 예제 - Entity 업데이트

// 실무 코드: DTO로 Entity 업데이트
data class ItgServiceCatalog(
    val itgCatalogId: String,
    val name: String,
    val description: String,
    val updatedAt: LocalDateTime
)

data class ItgServiceUpdateDto(
    val itgCatalogId: String? = null,
    val name: String? = null,
    val description: String? = null
)

fun ItgServiceCatalog.updated(dto: ItgServiceUpdateDto) = this.copy(
    itgCatalogId = dto.itgCatalogId ?: this.itgCatalogId,
    name = dto.name ?: this.name,
    description = dto.description ?: this.description,
    updatedAt = LocalDateTime.now()
)

// 사용
val original = ItgServiceCatalog(
    itgCatalogId = "SERVICE-001",
    name = "원본",
    description = "설명",
    updatedAt = LocalDateTime.now()
)

val updateDto = ItgServiceUpdateDto(name = "수정됨")
val updated = original.updated(updateDto)
// itgCatalogId, description은 유지, name만 변경

4.6 apply - 객체 초기화

용도: 객체 생성 후 속성 설정

// 기본 사용
val user = User().apply {
    id = 1
    name = "김철수"
    email = "kim@example.com"
}

// 실전 예제 - Builder 패턴 대체
data class EmailRequest(
    var to: String = "",
    var subject: String = "",
    var body: String = "",
    var cc: List<String> = emptyList()
)

val email = EmailRequest().apply {
    to = "user@example.com"
    subject = "안녕하세요"
    body = "메일 본문입니다"
    cc = listOf("cc1@example.com", "cc2@example.com")
}

4.7 run - 객체에서 계산

용도: 객체의 여러 메서드를 호출하고 결과 반환

// 기본 사용
val result = "Hello".run {
    println(this)
    println(this.length)
    this.uppercase()
}
println(result)  // "HELLO"

// 실전 예제
data class Rectangle(val width: Int, val height: Int)

val area = Rectangle(10, 20).run {
    println("가로: $width, 세로: $height")
    width * height
}
println("넓이: $area")  // "넓이: 200"

4.8 with - 여러 메서드 호출

용도: Non-nullable 객체의 여러 메서드 호출

// 기본 사용
val numbers = mutableListOf(1, 2, 3)
with(numbers) {
    add(4)
    add(5)
    println("크기: $size")
}

// 실전 예제 - StringBuilder
val html = with(StringBuilder()) {
    append("<html>")
    append("<body>")
    append("<h1>제목</h1>")
    append("</body>")
    append("</html>")
    toString()
}

4.9 also - 부가 작업

용도: 객체를 변경하지 않고 부가 작업 수행

// 기본 사용
val numbers = mutableListOf(1, 2, 3)
    .also { println("원본: $it") }
    .also { it.add(4) }
    .also { println("추가 후: $it") }

// 실전 예제 - 로깅
fun createUser(name: String): User {
    return User(name = name)
        .also { println("사용자 생성: ${it.name}") }
        .also { saveToDatabase(it) }
        .also { sendWelcomeEmail(it) }
}

5. 실전 패턴 모음

5.1 패턴 1: 체이닝 (Chaining)

// 여러 연산을 한 번에
val result = users
    .filter { it.isActive }
    .filter { it.age >= 18 }
    .map { it.name }
    .sorted()
    .take(10)

// 실무 예제: 복잡한 데이터 처리
data class Order(
    val id: Long,
    val userId: Long,
    val amount: Int,
    val status: String,
    val createdAt: LocalDateTime
)

val topSpenders = orders
    .filter { it.status == "COMPLETED" }
    .groupBy { it.userId }
    .mapValues { (_, orders) -> 
        orders.sumOf { it.amount } 
    }
    .toList()
    .sortedByDescending { it.second }
    .take(10)
    .map { (userId, totalAmount) ->
        SpenderInfo(userId, totalAmount)
    }

5.2 패턴 2: Null 안전 체이닝

// Safe Call + let 조합
val userEmail = userId
    ?.let { userRepository.findById(it) }
    ?.let { user -> user.email }
    ?.takeIf { it.isNotBlank() }
    ?.lowercase()

// 실무 예제: 복잡한 Null 처리
fun getServiceManagerEmails(serviceId: String?): List<String> {
    return serviceId
        ?.let { serviceRepository.findById(it) }
        ?.let { service -> service.managerIds }
        ?.mapNotNull { managerId -> 
            userRepository.findById(managerId) 
        }
        ?.mapNotNull { user -> user.email }
        ?.filter { it.contains("@company.com") }
        ?: emptyList()
}

5.3 패턴 3: 조건부 처리

// when과 함께
fun processUser(user: User) {
    when {
        user.age < 18 -> println("미성년자")
        user.age in 18..65 -> users
            .filter { it.isActive }
            .forEach { println("성인 활성: ${it.name}") }
        else -> println("시니어")
    }
}

// 실무 예제: 타입별 처리
fun createAgitPost(
    catalogType: CatalogType,
    name: String,
    description: String
): AgitPostRequest {
    return when (catalogType) {
        CatalogType.BIZ -> AgitPostRequest(
            channelId = "biz-channel",
            title = "[BIZ] $name",
            content = description
        )
        CatalogType.TECH -> AgitPostRequest(
            channelId = "tech-channel",
            title = "[TECH] $name",
            content = description
        )
    }
}

5.4 패턴 4: 데이터 변환 파이프라인

// 실무 예제: 복잡한 데이터 변환
fun getServiceSummaries(myOwn: Boolean?): List<ServiceSummary> {
    // 1단계: 서비스 조회
    val services = if (myOwn == true) {
        getMyOwnServices()
    } else {
        getAllServices()
    }
    
    // 2단계: 매니저 정보 조회
    val serviceIds = services.map { it.id }
    val managersMap = getManagersForServices(serviceIds)
    
    // 3단계: 응답 DTO 생성
    return services
        .map { service ->
            ServiceSummary(
                id = service.id,
                name = service.name,
                managers = managersMap[service.id] ?: emptyList()
            )
        }
        .sortedBy { it.name }
}

5.5 패턴 5: 그룹화 + 집계

// 실무 예제: 권한 그룹 집계
data class AuthGroup(
    val serviceId: String,
    val groupType: String,
    val userIds: List<String>
)

fun aggregateAuthGroups(
    groups: List<AuthGroup>
): Map<String, Map<String, List<String>>> {
    return groups
        .groupBy { it.serviceId }
        .mapValues { (_, serviceGroups) ->
            serviceGroups
                .groupBy { it.groupType }
                .mapValues { (_, typeGroups) ->
                    typeGroups.flatMap { it.userIds }.distinct()
                }
        }
}

// 사용 결과:
// {
//   "SERVICE-001": {
//     "admin": ["user1", "user2"],
//     "member": ["user3", "user4", "user5"]
//   },
//   "SERVICE-002": {
//     "admin": ["user6"]
//   }
// }

6. 성능 최적화 팁

6.1 Sequence 사용 - 지연 평가

// List - 즉시 평가 (매번 중간 List 생성)
val result1 = (1..1_000_000)
    .map { it * 2 }      // 100만개 List 생성
    .filter { it > 100 } // 또 다른 List 생성
    .take(10)

// Sequence - 지연 평가 (필요할 때만 계산)
val result2 = (1..1_000_000).asSequence()
    .map { it * 2 }
    .filter { it > 100 }
    .take(10)
    .toList()  // 여기서 최종 실행

// 성능 차이: 약 10배 이상!

언제 Sequence를 사용할까?

  • 데이터가 크고 (10,000개 이상)
  • 여러 연산을 체이닝하고
  • 최종 결과가 일부만 필요할 때

6.2 inline 함수 활용

// inline - Lambda 오버헤드 제거
inline fun <T> Iterable<T>.filterIf(
    condition: Boolean,
    predicate: (T) -> Boolean
): Iterable<T> {
    return if (condition) this.filter(predicate) else this
}
// inline으로 선언하면 컴파일 시 코드가 인라인으로 삽입됨

6.3 불필요한 중간 Collection 제거

// 나쁜 예 - 중간 List 2개 생성
val result = users
    .map { it.id }        // List<Long> 생성
    .filter { it > 100 }  // 또 다른 List<Long> 생성
    .toSet()

// 좋은 예 - 한 번에 처리
val result = users
    .filter { it.id > 100 }
    .mapTo(mutableSetOf()) { it.id }

6.4 Early Return 패턴

// 나쁜 예 - 모든 데이터 처리 후 판단
fun hasActiveUser(users: List<User>): Boolean {
    val activeUsers = users.filter { it.isActive }
    return activeUsers.isNotEmpty()
}

// 좋은 예 - 첫 번째 발견 시 즉시 반환
fun hasActiveUser(users: List<User>): Boolean {
    return users.any { it.isActive }
}

7. 자주 하는 실수와 해결법

❌ 실수 1: 불필요한 toList() 호출

// 나쁜 예
fun getActiveUsers(users: List<User>): List<User> {
    return users
        .filter { it.isActive }
        .toList()  // 이미 List인데 또 toList() 호출
}

// 좋은 예
fun getActiveUsers(users: List<User>): List<User> {
    return users.filter { it.isActive }
}

❌ 실수 2: forEach 남용

// 나쁜 예 - forEach로 변환
val names = mutableListOf<String>()
users.forEach { names.add(it.name) }

// 좋은 예 - map 사용
val names = users.map { it.name }

❌ 실수 3: 복잡한 Lambda 로직

// 나쁜 예 - Lambda 안에 복잡한 로직
val result = users.map { user ->
    if (user.age >= 18) {
        if (user.isActive) {
            val address = user.address
            if (address != null) {
                address.city
            } else {
                "Unknown"
            }
        } else {
            "Inactive"
        }
    } else {
        "Minor"
    }
}

// 좋은 예 - 별도 함수로 분리
fun getUserCity(user: User): String = when {
    user.age < 18 -> "Minor"
    !user.isActive -> "Inactive"
    else -> user.address?.city ?: "Unknown"
}

val result = users.map { getUserCity(it) }

8. Java Stream API vs Kotlin Collection

8.1 문법 비교

// Java Stream
List<String> names = users.stream()
    .filter(user -> user.isActive())
    .map(User::getName)
    .sorted()
    .collect(Collectors.toList());
// Kotlin Collection
val names = users
    .filter { it.isActive }
    .map { it.name }
    .sorted()

8.2 성능 비교

항목 Java Stream Kotlin Collection

기본 동작 지연 평가 즉시 평가
Parallel 지원 O X (Coroutines 사용)
간결성 보통 매우 높음
학습 곡선 높음 낮음

💡 핵심 요약 (10초 복습)

// 1. Lambda
{ x, y -> x + y }
{ it * 2 }

// 2. Collection Operations
list.map { it * 2 }
list.filter { it > 5 }
list.groupBy { it.category }

// 3. Scope Functions
value?.let { /* null 아닐 때 */ }
object.apply { /* 초기화 */ }
object.copy(name = "새이름")

// 4. 체이닝
users
    .filter { it.isActive }
    .map { it.name }
    .sorted()

📊 Operator 선택 가이드

작업 사용할 함수 예제

변환 map users.map { it.name }
필터링 filter users.filter { it.age >= 18 }
그룹화 groupBy users.groupBy { it.city }
Map 생성 associate users.associate { it.id to it.name }
Null 제거 변환 mapNotNull list.mapNotNull { it.toIntOrNull() }
Null 안전 처리 let value?.let { use(it) }
객체 초기화 apply User().apply { name = "Kim" }
일부 수정 copy user.copy(age = 31)

❓ FAQ (자주 묻는 질문)

Q1. map과 forEach의 차이는?

map은 변환 결과를 새 List로 반환, forEach는 각 요소에 대해 작업만 수행하고 반환값 없음. 변환이 필요하면 map, 단순 반복 작업이면 forEach를 사용하세요.

Q2. let과 apply의 차이는?

let은 Lambda 결과를 반환하고 객체를 it으로 참조, apply는 객체 자신을 반환하고 this로 참조합니다. 변환이 필요하면 let, 초기화가 필요하면 apply를 사용하세요.

Q3. Sequence는 언제 사용하나요?

데이터가 많고(10,000개 이상), 여러 연산을 체이닝하고, 최종 결과가 일부만 필요할 때 사용하세요. 성능이 크게 향상됩니다.

Q4. groupBy vs groupingBy 차이는?

groupBy는 즉시 Map을 생성, groupingBy는 집계 연산과 함께 사용. 단순 그룹화는 groupBy, 그룹별 집계(count, sum 등)는 groupingBy를 사용하세요.

Q5. Java Stream과 성능 차이는?

소규모 데이터(< 10,000)에서는 거의 동일. 대규모에서는 Kotlin Sequence가 약간 빠름. 하지만 가독성과 간결성에서 Kotlin이 훨씬 우수합니다.


🎯 실전 과제

과제 1: 기본 Collection 연산

data class Product(
    val id: Long,
    val name: String,
    val price: Int,
    val category: String
)

val products = listOf(
    Product(1, "노트북", 1_500_000, "전자기기"),
    Product(2, "마우스", 30_000, "전자기기"),
    Product(3, "책상", 200_000, "가구"),
    Product(4, "의자", 150_000, "가구")
)

// TODO 1: 전자기기 카테고리만 필터링
// TODO 2: 가격이 100,000원 이상인 제품만
// TODO 3: 제품명 리스트로 변환
// TODO 4: 알파벳 순 정렬

과제 2: 복잡한 데이터 변환

data class Order(
    val id: Long,
    val userId: Long,
    val amount: Int,
    val status: String
)

// TODO: 사용자별 총 주문 금액 계산
// 결과: Map<Long, Int> (userId to totalAmount)
// 힌트: groupBy, mapValues, sumOf

과제 3: Scope Function 활용

data class User(
    var id: Long = 0,
    var name: String = "",
    var email: String = ""
)

// TODO: User 객체를 생성하고 초기화
// apply를 사용하여 id=1, name="김철수", email="kim@example.com" 설정

정답과 해설은 댓글로! 😊


📢 마치며

Kotlin의 함수형 프로그래밍을 완벽하게 마스터하셨습니다!

이제 여러분은:

  • ✅ Lambda로 간결한 코드를 작성할 수 있습니다
  • ✅ map, filter, groupBy로 데이터를 자유자재로 다룰 수 있습니다
  • ✅ Scope Functions로 코드 가독성을 높일 수 있습니다
  • ✅ Java보다 10배 간결한 코드를 작성할 수 있습니다

다음 글 예고: 다음에는 Extension FunctionsString 처리 기법을 다룹니다. 기존 클래스에 새로운 기능을 추가하는 마법 같은 기능과 String Templates, Multi-line String을 마스터해보세요!

궁금한 점은 댓글로 남겨주세요! 💬

반응형
Comments