Notice
Recent Posts
Recent Comments
Link
반응형
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | |
| 7 | 8 | 9 | 10 | 11 | 12 | 13 |
| 14 | 15 | 16 | 17 | 18 | 19 | 20 |
| 21 | 22 | 23 | 24 | 25 | 26 | 27 |
| 28 | 29 | 30 | 31 |
Tags
- 알고리즘
- ElasticSearch
- 티스토리챌린지
- 스프링
- 이펙티브 자바
- 이차전지관련주
- 오블완
- Effective Java 3
- java
- 엘라스틱서치
- 이펙티브자바
- MongoDB
- 스프링 핵심원리
- 자바스크립트
- kubernetes
- 스프링부트
- k8s
- 자바
- 카카오
- 알고리즘정렬
- Spring
- 예제로 배우는 스프링 입문
- 클린아키텍처
- Sort
- effectivejava
- 김영한
- 스프링핵심원리
- Kotlin
- JavaScript
- Effective Java
Archives
- Today
- Total
Kim-Baek 개발자 이야기
Kotlin 컬렉션 완벽 가이드 | List, Set, Map 함수형 프로그래밍 본문
반응형

이 글을 읽으면: Java의 Stream API보다 강력한 Kotlin 컬렉션 함수를 배울 수 있습니다. map, filter, groupBy 등 함수형 프로그래밍으로 데이터를 우아하게 처리하는 방법을 실전 예제로 마스터하세요.
📌 목차
- 들어가며 - Java 컬렉션의 불편함
- 컬렉션 생성 - 불변 vs 가변
- List 컬렉션 완벽 정리
- Set과 Map
- 함수형 연산 - map, filter, reduce
- 고급 연산 - groupBy, partition, flatMap
- Sequence - 성능 최적화
- 실전 패턴 모음
- 마무리 - 다음 편 예고
들어가며 - Java 컬렉션의 불편함
Java로 데이터 처리할 때 이런 경험 있으신가요?
// Java - 장황한 컬렉션 처리
List<User> users = Arrays.asList(
new User("규철", 30),
new User("영희", 25),
new User("철수", 35)
);
// 1. 30세 이상 필터링
List<User> adults = new ArrayList<>();
for (User user : users) {
if (user.getAge() >= 30) {
adults.add(user);
}
}
// 2. 이름만 추출
List<String> names = new ArrayList<>();
for (User user : adults) {
names.add(user.getName());
}
// 3. 대문자로 변환
List<String> upperNames = new ArrayList<>();
for (String name : names) {
upperNames.add(name.toUpperCase());
}
// Java 8 Stream - 조금 나아짐
List<String> result = users.stream()
.filter(u -> u.getAge() >= 30)
.map(User::getName)
.map(String::toUpperCase)
.collect(Collectors.toList());
Kotlin은 이렇게 간단합니다:
// Kotlin - 한 줄로 끝
val users = listOf(
User("규철", 30),
User("영희", 25),
User("철수", 35)
)
val result = users
.filter { it.age >= 30 }
.map { it.name.uppercase() }
println(result) // [규철, 철수]
오늘은 Kotlin의 강력한 컬렉션 API를 완전히 마스터해보겠습니다!
컬렉션 생성 - 불변 vs 가변
불변 컬렉션 (읽기 전용)
기본적으로 불변 컬렉션을 사용하세요!
fun main() {
// List - listOf
val numbers = listOf(1, 2, 3, 4, 5)
// numbers.add(6) // ❌ 컴파일 에러 (불변)
// Set - setOf
val uniqueNumbers = setOf(1, 2, 2, 3, 3, 3)
println(uniqueNumbers) // [1, 2, 3] (중복 제거)
// Map - mapOf
val scores = mapOf(
"국어" to 90,
"영어" to 85,
"수학" to 95
)
println(scores["국어"]) // 90
}
가변 컬렉션 (읽기/쓰기)
fun main() {
// MutableList
val numbers = mutableListOf(1, 2, 3)
numbers.add(4)
numbers.remove(2)
println(numbers) // [1, 3, 4]
// MutableSet
val items = mutableSetOf("사과", "바나나")
items.add("오렌지")
items.add("사과") // 중복이므로 추가 안 됨
println(items) // [사과, 바나나, 오렌지]
// MutableMap
val scores = mutableMapOf("국어" to 90)
scores["영어"] = 85
scores["수학"] = 95
println(scores) // {국어=90, 영어=85, 수학=95}
}
빈 컬렉션 생성
fun main() {
// 불변 빈 컬렉션
val emptyList = emptyList<String>()
val emptySet = emptySet<Int>()
val emptyMap = emptyMap<String, Int>()
// 가변 빈 컬렉션
val mutableList = mutableListOf<String>()
val mutableSet = mutableSetOf<Int>()
val mutableMap = mutableMapOf<String, Int>()
}
Java 스타일 vs Kotlin 스타일
// Java - 타입 중복, 길고 복잡함
List<String> list = new ArrayList<>();
list.add("사과");
list.add("바나나");
Map<String, Integer> map = new HashMap<>();
map.put("국어", 90);
map.put("영어", 85);
// Kotlin - 간결하고 타입 추론
val list = mutableListOf("사과", "바나나")
val map = mutableMapOf(
"국어" to 90,
"영어" to 85
)
List 컬렉션 완벽 정리
기본 연산
fun main() {
val numbers = listOf(1, 2, 3, 4, 5)
// 크기
println(numbers.size) // 5
// 인덱스 접근
println(numbers[0]) // 1
println(numbers.first()) // 1
println(numbers.last()) // 5
// 안전한 접근
println(numbers.getOrNull(10)) // null
println(numbers.getOrElse(10) { 0 }) // 0
// 포함 확인
println(numbers.contains(3)) // true
println(3 in numbers) // true
// 인덱스 찾기
println(numbers.indexOf(3)) // 2
}
슬라이싱
fun main() {
val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
// 처음 3개
println(numbers.take(3)) // [1, 2, 3]
// 마지막 3개
println(numbers.takeLast(3)) // [8, 9, 10]
// 처음 3개 제외
println(numbers.drop(3)) // [4, 5, 6, 7, 8, 9, 10]
// 범위로 자르기
println(numbers.slice(2..5)) // [3, 4, 5, 6]
// 조건에 맞는 동안만
println(numbers.takeWhile { it < 5 }) // [1, 2, 3, 4]
}
정렬
fun main() {
val numbers = listOf(5, 2, 8, 1, 9, 3)
// 오름차순
println(numbers.sorted()) // [1, 2, 3, 5, 8, 9]
// 내림차순
println(numbers.sortedDescending()) // [9, 8, 5, 3, 2, 1]
// 커스텀 정렬
data class Person(val name: String, val age: Int)
val people = listOf(
Person("규철", 30),
Person("영희", 25),
Person("철수", 35)
)
// 나이순 정렬
println(people.sortedBy { it.age })
// [Person(name=영희, age=25), Person(name=규철, age=30), Person(name=철수, age=35)]
// 나이 내림차순
println(people.sortedByDescending { it.age })
}
리스트 조작
fun main() {
val list1 = listOf(1, 2, 3)
val list2 = listOf(4, 5, 6)
// 합치기
println(list1 + list2) // [1, 2, 3, 4, 5, 6]
// 중복 제거
val duplicates = listOf(1, 2, 2, 3, 3, 3)
println(duplicates.distinct()) // [1, 2, 3]
// 뒤집기
println(list1.reversed()) // [3, 2, 1]
// 섞기
println(list1.shuffled()) // 랜덤 순서
}
Set과 Map
Set - 중복 없는 컬렉션
fun main() {
val set1 = setOf(1, 2, 3)
val set2 = setOf(3, 4, 5)
// 합집합
println(set1 + set2) // [1, 2, 3, 4, 5]
println(set1.union(set2)) // [1, 2, 3, 4, 5]
// 교집합
println(set1.intersect(set2)) // [3]
// 차집합
println(set1 - set2) // [1, 2]
println(set1.subtract(set2)) // [1, 2]
}
Map - 키-값 쌍
fun main() {
val scores = mapOf(
"국어" to 90,
"영어" to 85,
"수학" to 95
)
// 값 가져오기
println(scores["국어"]) // 90
println(scores.get("국어")) // 90
println(scores.getValue("국어")) // 90 (없으면 예외)
println(scores.getOrDefault("과학", 0)) // 0
// 키/값 목록
println(scores.keys) // [국어, 영어, 수학]
println(scores.values) // [90, 85, 95]
// 순회
for ((subject, score) in scores) {
println("$subject: $score점")
}
// 필터링
val highScores = scores.filter { it.value >= 90 }
println(highScores) // {국어=90, 수학=95}
}
가변 Map 조작
fun main() {
val scores = mutableMapOf(
"국어" to 90,
"영어" to 85
)
// 추가/수정
scores["수학"] = 95
scores.put("과학", 88)
// 없을 때만 추가
scores.putIfAbsent("국어", 100) // 이미 있으므로 무시
// 삭제
scores.remove("영어")
// 조건부 수정
scores.compute("수학") { _, value ->
(value ?: 0) + 5 // 100
}
println(scores)
}
함수형 연산 - map, filter, reduce
filter - 조건에 맞는 요소만
fun main() {
val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
// 짝수만
val evens = numbers.filter { it % 2 == 0 }
println(evens) // [2, 4, 6, 8, 10]
// 5보다 큰 수
val greaterThan5 = numbers.filter { it > 5 }
println(greaterThan5) // [6, 7, 8, 9, 10]
// 조건에 안 맞는 것만 (반대)
val odds = numbers.filterNot { it % 2 == 0 }
println(odds) // [1, 3, 5, 7, 9]
}
map - 변환
fun main() {
val numbers = listOf(1, 2, 3, 4, 5)
// 제곱
val squares = numbers.map { it * it }
println(squares) // [1, 4, 9, 16, 25]
// 문자열로 변환
val strings = numbers.map { "숫자: $it" }
println(strings) // [숫자: 1, 숫자: 2, ...]
data class Person(val name: String, val age: Int)
val people = listOf(
Person("규철", 30),
Person("영희", 25)
)
// 이름만 추출
val names = people.map { it.name }
println(names) // [규철, 영희]
}
체이닝 - filter + map
fun main() {
data class Product(val name: String, val price: Int, val category: String)
val products = listOf(
Product("노트북", 1500000, "전자제품"),
Product("마우스", 30000, "전자제품"),
Product("책상", 200000, "가구"),
Product("의자", 150000, "가구")
)
// 전자제품 중 5만원 이상인 것의 이름만
val result = products
.filter { it.category == "전자제품" }
.filter { it.price >= 50000 }
.map { it.name }
println(result) // [노트북]
}
reduce - 누적 계산
fun main() {
val numbers = listOf(1, 2, 3, 4, 5)
// 합계
val sum = numbers.reduce { acc, num -> acc + num }
println(sum) // 15
// 곱셈
val product = numbers.reduce { acc, num -> acc * num }
println(product) // 120
// fold - 초기값 지정 가능
val sumWith100 = numbers.fold(100) { acc, num -> acc + num }
println(sumWith100) // 115 (100 + 15)
}
실전 예제 - 주문 처리
data class Order(
val id: Long,
val customerName: String,
val amount: Int,
val status: String
)
fun main() {
val orders = listOf(
Order(1, "규철", 50000, "완료"),
Order(2, "영희", 30000, "완료"),
Order(3, "철수", 100000, "취소"),
Order(4, "규철", 25000, "완료")
)
// 완료된 주문의 총 금액
val totalAmount = orders
.filter { it.status == "완료" }
.sumOf { it.amount }
println("총 매출: ${totalAmount}원") // 105000원
// 고객별 구매 금액
val customerTotals = orders
.filter { it.status == "완료" }
.groupBy { it.customerName }
.mapValues { (_, orders) -> orders.sumOf { it.amount } }
println(customerTotals) // {규철=75000, 영희=30000}
}
고급 연산 - groupBy, partition, flatMap
groupBy - 그룹화
fun main() {
data class Student(val name: String, val grade: Int, val score: Int)
val students = listOf(
Student("규철", 1, 90),
Student("영희", 2, 85),
Student("철수", 1, 88),
Student("민수", 2, 92)
)
// 학년별 그룹화
val byGrade = students.groupBy { it.grade }
println(byGrade)
// {1=[Student(규철, 1, 90), Student(철수, 1, 88)],
// 2=[Student(영희, 2, 85), Student(민수, 2, 92)]}
// 학년별 평균 점수
val averageByGrade = students
.groupBy { it.grade }
.mapValues { (_, students) ->
students.map { it.score }.average()
}
println(averageByGrade) // {1=89.0, 2=88.5}
}
partition - 둘로 나누기
fun main() {
val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
// 짝수와 홀수로 분리
val (evens, odds) = numbers.partition { it % 2 == 0 }
println("짝수: $evens") // [2, 4, 6, 8, 10]
println("홀수: $odds") // [1, 3, 5, 7, 9]
}
flatMap - 평탄화
fun main() {
val nested = listOf(
listOf(1, 2, 3),
listOf(4, 5),
listOf(6, 7, 8, 9)
)
// 중첩 리스트를 평탄화
val flattened = nested.flatMap { it }
println(flattened) // [1, 2, 3, 4, 5, 6, 7, 8, 9]
// 변환 + 평탄화
val result = nested.flatMap { list ->
list.map { it * 2 }
}
println(result) // [2, 4, 6, 8, 10, 12, 14, 16, 18]
}
실전 예제 - 문자열 처리
fun main() {
val text = "Kotlin is awesome! Kotlin is fun!"
// 단어 빈도수
val wordCount = text
.lowercase()
.split(" ", "!", ".")
.filter { it.isNotEmpty() }
.groupBy { it }
.mapValues { (_, words) -> words.size }
.toList()
.sortedByDescending { it.second }
println(wordCount)
// [(kotlin, 2), (is, 2), (awesome, 1), (fun, 1)]
}
associate - Map 생성
fun main() {
data class User(val id: Long, val name: String)
val users = listOf(
User(1, "규철"),
User(2, "영희"),
User(3, "철수")
)
// id를 키로, name을 값으로
val userMap = users.associate { it.id to it.name }
println(userMap) // {1=규철, 2=영희, 3=철수}
// name을 키로, User를 값으로
val nameMap = users.associateBy { it.name }
println(nameMap["규철"]) // User(id=1, name=규철)
}
Sequence - 성능 최적화
List vs Sequence
fun main() {
val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
// List - 즉시 평가 (각 연산마다 새 리스트 생성)
val listResult = numbers
.map {
println("map: $it")
it * 2
}
.filter {
println("filter: $it")
it > 10
}
.take(2)
println("=== Sequence ===")
// Sequence - 지연 평가 (필요할 때만 계산)
val seqResult = numbers.asSequence()
.map {
println("map: $it")
it * 2
}
.filter {
println("filter: $it")
it > 10
}
.take(2)
.toList()
}
언제 Sequence를 사용할까?
fun main() {
// ✅ Sequence 사용 권장
// 1. 대용량 데이터
val largeData = (1..1_000_000).asSequence()
.filter { it % 2 == 0 }
.map { it * 2 }
.take(100)
.toList()
// 2. 체인이 긴 경우
val result = (1..100).asSequence()
.filter { it % 2 == 0 }
.map { it * 2 }
.filter { it > 50 }
.map { it.toString() }
.toList()
// ❌ Sequence 불필요
// 작은 데이터 + 간단한 연산
val simple = listOf(1, 2, 3, 4, 5)
.filter { it % 2 == 0 }
.map { it * 2 }
}
generateSequence - 무한 시퀀스
fun main() {
// 피보나치 수열
val fibonacci = generateSequence(0 to 1) { (a, b) ->
b to (a + b)
}.map { it.first }
// 처음 10개만
println(fibonacci.take(10).toList())
// [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
// 1부터 시작하는 무한 수열
val infiniteNumbers = generateSequence(1) { it + 1 }
// 100 이하만
println(infiniteNumbers.takeWhile { it <= 100 }.toList())
}
실전 패턴 모음
패턴 1: 데이터 집계
data class Sale(
val date: String,
val product: String,
val amount: Int,
val quantity: Int
)
fun main() {
val sales = listOf(
Sale("2024-01", "노트북", 1500000, 2),
Sale("2024-01", "마우스", 30000, 5),
Sale("2024-02", "노트북", 1500000, 1),
Sale("2024-02", "키보드", 100000, 3)
)
// 월별 총 매출
val monthlyTotal = sales
.groupBy { it.date }
.mapValues { (_, sales) ->
sales.sumOf { it.amount * it.quantity }
}
println(monthlyTotal)
// {2024-01=3150000, 2024-02=1800000}
// 제품별 판매량
val productQuantity = sales
.groupBy { it.product }
.mapValues { (_, sales) ->
sales.sumOf { it.quantity }
}
println(productQuantity)
// {노트북=3, 마우스=5, 키보드=3}
}
패턴 2: 중복 제거 및 정렬
data class User(val id: Long, val name: String, val email: String)
fun main() {
val users = listOf(
User(1, "규철", "user1@example.com"),
User(2, "영희", "user2@example.com"),
User(1, "규철", "user1@example.com"), // 중복
User(3, "철수", "user3@example.com")
)
// ID 기준 중복 제거 + 이름순 정렬
val uniqueSorted = users
.distinctBy { it.id }
.sortedBy { it.name }
println(uniqueSorted)
}
패턴 3: 조건별 분류
data class Student(val name: String, val score: Int)
fun main() {
val students = listOf(
Student("규철", 95),
Student("영희", 78),
Student("철수", 88),
Student("민수", 65),
Student("수지", 92)
)
// 성적별 분류
val gradeGroups = students.groupBy { student ->
when {
student.score >= 90 -> "A"
student.score >= 80 -> "B"
student.score >= 70 -> "C"
else -> "D"
}
}
println(gradeGroups)
// {A=[규철(95), 수지(92)], C=[영희(78)], B=[철수(88)], D=[민수(65)]}
}
패턴 4: 체이닝 최적화
fun main() {
val numbers = (1..1000).toList()
// ❌ 비효율적 - 여러 번 순회
val bad = numbers
.filter { it % 2 == 0 } // 첫 번째 순회
.map { it * 2 } // 두 번째 순회
.filter { it > 100 } // 세 번째 순회
// ✅ 효율적 - Sequence 사용
val good = numbers.asSequence()
.filter { it % 2 == 0 }
.map { it * 2 }
.filter { it > 100 }
.toList() // 한 번만 순회
}
마무리 - 다음 편 예고
오늘 배운 것 ✅
- 불변/가변 컬렉션 (listOf, mutableListOf)
- List, Set, Map 기본 연산
- 함수형 연산 - filter, map, reduce
- 고급 연산 - groupBy, partition, flatMap
- Sequence - 성능 최적화
다음 편에서 배울 것 📚
8편: 람다와 고차 함수 완벽 가이드 | 함수형 프로그래밍 심화
- 람다 표현식 완벽 정리
- 고차 함수 (함수를 인자/반환)
- 수신 객체 지정 람다
- inline, crossinline, noinline
- 실전 DSL 패턴
실습 과제 💪
// 1. 데이터 필터링 및 변환
// - 상품 리스트에서 특정 조건 필터링
// - 가격 합계, 평균 계산
// 2. groupBy 활용
// - 주문 데이터를 고객별로 그룹화
// - 고객별 총 구매액 계산
// 3. Sequence 성능 비교
// - List vs Sequence 벤치마크
// - 대용량 데이터 처리
// 4. 실전 패턴
// - 로그 데이터 분석
// - 에러 빈도수 집계
자주 묻는 질문 (FAQ)
Q: List와 Sequence 중 뭘 써야 하나요?
A: 작은 데이터나 단순 연산은 List, 대용량이나 긴 체인은 Sequence를 쓰세요.
Q: map과 forEach의 차이는?
A: map은 새 리스트 반환, forEach는 반환값 없이 실행만 합니다.
Q: filter 여러 개 vs 조건 합치기?
A: 가독성이 좋으면 여러 개도 OK. Sequence면 성능 차이 없습니다.
Q: groupBy와 partition의 차이는?
A: groupBy는 여러 그룹, partition은 딱 두 그룹으로 나눕니다.
관련 글
💬 댓글로 알려주세요!
- 어떤 컬렉션 함수가 가장 유용했나요?
- Sequence를 실전에서 사용해본 경험이 있나요?
- 이 글이 도움이 되셨나요?
태그: #Kotlin #컬렉션 #함수형프로그래밍 #map #filter #groupBy #Sequence
반응형
'개발 > java basic' 카테고리의 다른 글
| Kotlin Null Safety 완벽 가이드 | ?, ?., ?:, !! 마스터하기 (0) | 2025.12.23 |
|---|---|
| Kotlin 변수와 타입 완벽 가이드 | val vs var, 타입 추론, Nullable 타입 마스터하기 (0) | 2025.12.22 |
| Kotlin 클래스와 객체 완벽 가이드 | OOP의 Kotlin 스타일 (0) | 2025.12.22 |
| Kotlin 제어 구조 완벽 가이드 | if, when, for, while 마스터하기 (0) | 2025.12.21 |
| Kotlin 함수 완벽 가이드 | fun으로 시작하는 간결한 코드 (0) | 2025.12.21 |
Comments
