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 컬렉션 설계는 "개발자가 실수하지 않도록 도와주는 안전한 기본값"을 제공합니다.
방어적 복사, 멀티스레드 안정성, 예측 가능한 로직 등 실무에서 얻는 이점이 매우 큽니다.

 

🤔 추가로 생각해 볼 질문들

  1. Kotlin의 List는 실제로 어떤 구현체일까?
  2. Java의 Collections.unmodifiableList()와 Kotlin listOf()는 어떤 차이가 있을까?
  3. val list = mutableListOf(...) 일 때, val은 무엇을 의미할까?
  4. sequence를 활용한 컬렉션 처리의 장점은?
  5. toList()는 깊은 복사일까, 얕은 복사일까?

 

Reference