with ChatGPT/코틀린 with ChatGPT
[코틀린 with ChatGPT #16] Kotlin에서 in-place 변환을 지원하는 mutable 컬렉션 vs immutable 컬렉션의 차이점과 장단점
dev_writer
2025. 3. 30. 09:00
with ChatGPT 시리즈는 ChatGPT의 내용과 개인의 생각을 토대로 학습해 보는 컨텐츠입니다.
Kotlin은 컬렉션 API에서 불변성 (immutability)을 기본 철학으로 합니다.
하지만 실무에서는 종종 가변성 (mutable)도 필요하기 때문에, Kotlin은 두 가지 컬렉션 버전을 동시에 제공합니다.
🔹 Kotlin 컬렉션의 기본 원칙
Kotlin에서 기본적으로 생성되는 컬렉션 (listOf, setOf, mapOf)은 읽기 전용 컬렉션 (immutable)입니다.
val fruits = listOf("Apple", "Banana", "Orange")
// fruits.add("Mango") // ❌ 컴파일 에러
- listOf로 만든 리스트는 크기나 요소를 변경할 수 없음
- 내부적으로는 Java의 Collections.unmodifiedList() 같은 방식
🔹 List vs MutableList
구분 | List | MutableList |
변경 가능 여부 | ❌ 불가 (읽기 전용) | ✅ 가능 (add, remove 등) |
메서드 | get, contains, size 등 | add, remove, clear 등 추가됨 |
선언 방식 | listOf(...) | mutableListOf(...) |
val immutable = listOf("a", "b", "c")
val mutable = mutableListOf("a", "b", "c")
mutable.add("d") // ✅ 가능
🔹 toMutableList()와 toList() 차이
- toMutableList(): 불변 컬렉션을 가변으로 변환
val names = listOf("Tom", "Jerry")
val modifiable = names.toMutableList()
modifiable.add("Spike")
- toList(): 가변 컬렉션을 복사하여 불변화
val mutable = mutableListOf("A", "B")
val safeCopy = mutable.toList() // add 불가
🔹 in-place 변환 함수 예시
MutableList를 사용할 경우, 원본 데이터를 직접 수정 (in-place) 하는 것이 가능합니다.
val list = mutableListOf(3, 1, 4, 2)
list.sort() // [1, 2, 3, 4]
list.removeIf { it % 2 == 0 } // [1, 3]
list.replaceAll { it * 10 } // [10, 30]
in-place 함수는 원본 컬렉션을 직접 변경하므로, 사이드 이펙트를 고려해 신중하게 사용할 필요가 있습니다.
🔹 불변 컬렉션이 제공하는 방어적 복사 효과
Kotlin의 불변 컬렉션은 코드의 안정성과 예측 가능성을 높여주는 핵심 도구입니다.
특히 다음과 같은 상황에서 불변 컬렉션이 방어적 복사처럼 작동하며, 버그를 예방하는 데 효과적입니다.
- 외부에서 컬렉션을 넘겨받았을 때, 내부에서 변경이 일어날 수 있는 상황을 자동으로 차단
- 동시성 이슈 (멀티스레드 환경)에서 예기치 않은 공유 상태 변경 방지
- API 사용자에게 "이 컬렉션은 수정하지 마세요"라는 강력한 메시지를 타입으로 전달
Kotlin은 이러한 안전성을 명시적인 복사 없이도 자연스럽게 유도합니다.
✅ Java에서는 명시적인 방어적 복사가 필수
public class OrderService {
private final List<String> items;
public OrderService(List<String> items) {
this.items = new ArrayList<>(items); // 외부 변경 차단
}
}
✅ Kotlin에서는 기본 컬렉션이 불변이라 자연스럽게 안전
class OrderService(items: List<String>) {
private val items = items.toList() // 방어적 복사
}
또는 호출부에서 listOf(...)로 불변 컬렉션을 넘기면 복사조차 필요 없는 경우도 많습니다.
val service = OrderService(listOf("apple", "banana"))
Kotlin에서는 "방어적 복사를 신경 쓰지 않아도 되는" 코드 스타일이 기본이 됩니다.
이는 실수 방지뿐 아니라 API 안전성도 크게 높여줍니다.
🔹 장단점 비교
항목 | Immutable (List) | Mutable (MutableList) |
변경 가능 여부 | ❌ 불가 | ✅ 가능 |
사이드 이펙트 | 없음 | 있음 (원본 변경 주의) |
병렬 처리 안정성 | 높음 | 낮음 |
사용 목적 명확성 | 명확함 (val list = listOf(...)) | 실수 가능성 있음 |
성능 | 복사 시 오버헤드 가능 | 더 빠를 수 있음 |
방어적 복사 필요 | ✅ 대부분 불필요 | ❌ 수동 복사 필요 |
✅ 실무에서의 선택 기준
상황 | 추천 컬렉션 |
API 파라미터, 응답 데이터 | 불변 컬렉션 (List) |
내부 상태 조작, 임시 처리 | 가변 컬렉션 (MutableList) |
외부 변경 차단이 필요한 경우 | toList()로 감싸서 복사 |
Thread-safe 한 구조가 필요한 경우 | 불변 컬렉션 우선 고려 |
🚀 결론
항목 | 요약 |
Kotlin은 기본 컬렉션을 불변으로 제공 | listOf, mapOf, setOf 등 |
가변 컬렉션 사용 시 명시적으로 선언 필요 | mutableListOf, toMutableList() |
toList()로 방어적 복사 가능 | 외부 변경 방지에 유용 |
불변 컬렉션은 멀티스레드 환경에서도 안전 | 공유 상태를 방지함 |
실무에서는 불변 컬렉션을 기본으로 사용하고, 필요한 경우에만 mutable 사용 | 기본은 immutable, 예외적으로 mutable이라는 흐름 유지 |
Kotlin 컬렉션 설계는 "개발자가 실수하지 않도록 도와주는 안전한 기본값"을 제공합니다.
방어적 복사, 멀티스레드 안정성, 예측 가능한 로직 등 실무에서 얻는 이점이 매우 큽니다.
🤔 추가로 생각해 볼 질문들
- Kotlin의 List는 실제로 어떤 구현체일까?
- Java의 Collections.unmodifiableList()와 Kotlin listOf()는 어떤 차이가 있을까?
- val list = mutableListOf(...) 일 때, val은 무엇을 의미할까?
- sequence를 활용한 컬렉션 처리의 장점은?
- toList()는 깊은 복사일까, 얕은 복사일까?