일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- 암호학
- ProGuard
- livedata
- 프로그래머스
- architecture
- pandas
- activity
- Kotlin
- 백준
- AWS
- Hilt
- 코드포스
- Compose
- android
- Coroutines
- GitHub
- boj
- textfield
- MyVoca
- relay
- Coroutine
- Rxjava
- 코루틴
- TEST
- Codeforces
- androidStudio
- Python
- Gradle
- 쿠링
- MiTweet
- Today
- Total
이동식 저장소
Kotlin Immutable Collections 본문
GitHub - Kotlin/kotlinx.collections.immutable: Immutable persistent collections for Kotlin
Immutable persistent collections for Kotlin. Contribute to Kotlin/kotlinx.collections.immutable development by creating an account on GitHub.
github.com
List<T>
에는 리스트를 수정할 수 있는 메서드가 없지만, 어떤 리스트의 타입이 List<T>
라고 해서 리스트의 내용이 변경되지 않는다고 말할 수는 없다. MutableList<T>
를 List<T>
로 반환했을 지도 모르기 때문이다. Compose 컴파일러가 List<T>
를 unstable로 판단하는 이유이다.
하지만 Immutable Collections 라이브러리에는 어떠한 경우에도 내용이 변경되지 않는 collections 타입이 정의되어 있다. 내용을 변경해야 하는 경우에는 아예 새로운 객체를 만들어서 변경을 적용한 후 반환한다. Compose도 이것만큼은 stable로 인정한다.
구조

Immutable~
인터페이스는 완벽한 읽기 전용이다. 아예 함수조차 get
과 subList
뿐이다. Persistent~
인터페이스는 리스트의 수정을 지원하지만, 리스트의 사본에 변경 사항을 적용하여 반환하기 때문에 원래 리스트는 바뀌지 않는다.
딱 보면 답 나오지? Immutable
는 UI 등 앞단에서, Persistent
는 데이터를 관리하는 뒷단에서 사용하면 되겠다. UI에서는 데이터를 읽기만 하니까(정확히는 읽기만 해야 하니까). 반면 뒷단에서는 현재 데이터에 변경 사항을 적용하여 새로운 데이터를 제공해야 하는데, 이때 Persistent
인터페이스에 정의된 수정 함수를 사용하면 된다.
코드
이 글에서는ImmutableList
와PersistentList
만을 살펴본다.
ImmutableCollection
은 단순한 marking interface이다. 진짜 한 줄밖에 없다.

ImmutableList
에는 subList()
단 하나의 함수만 정의돼 있다. 코드를 안 봐도 알 것 같은..
그런데 subList()
의 반환 타입인 SubList<T>
는 볼 만하다. 그냥 ImmutableList
를 잘라서 반환하는 거 아닌가 싶지만 그 말대로라면 애초에 SubList
타입이 존재할 이유가 없다. SubList
에서 주목할 점은, 특정 리스트의 일부만을 표현함에도 불구하고 전체 리스트를 들고 있다는 점이다.
public interface ImmutableList<out E> : List<E>, ImmutableCollection<E> {
// 리스트 전체를 파라미터로 넘긴다.
fun subList(fromIndex: Int, toIndex: Int): ImmutableList<E> = SubList(this, fromIndex, toIndex)
private class SubList<E>(private val source: ImmutableList<E>, private val fromIndex: Int, private val toIndex: Int) : ImmutableList<E>, AbstractList<E>() {
private var _size: Int = 0
init {
ListImplementation.checkRangeIndexes(fromIndex, toIndex, source.size)
this._size = toIndex - fromIndex
}
override fun get(index: Int): E {
ListImplementation.checkElementIndex(index, _size)
return source[fromIndex + index]
}
override val size: Int get() = _size
override fun subList(fromIndex: Int, toIndex: Int): ImmutableList<E> {
ListImplementation.checkRangeIndexes(fromIndex, toIndex, this._size)
return SubList(source, this.fromIndex + fromIndex, this.fromIndex + toIndex)
}
}
}
보다시피 원본 리스트를 계속 참조한다. (의미상) 한번 자른 SubList
를 잘라도 여전히 똑같은 원본을 참조한다. 리스트를 매번 자르는 게 더 비효율적이라고 판단한 듯하다. 흠.. 나름 일리 있는 것 같기도 하고?
이제 PersistentCollection
을 보자. PersistentCollection
에는 몇 가지 수정 함수와, 수정 함수를 지원하기 위한 Builder
인터페이스가 정의돼 있다.
public interface PersistentCollection<out E> : ImmutableCollection<E> {
fun add(element: @UnsafeVariance E): PersistentCollection<E>
fun addAll(elements: Collection<@UnsafeVariance E>): PersistentCollection<E>
fun remove(element: @UnsafeVariance E): PersistentCollection<E>
fun removeAll(elements: Collection<@UnsafeVariance E>): PersistentCollection<E>
fun removeAll(predicate: (E) -> Boolean): PersistentCollection<E>
fun retainAll(elements: Collection<@UnsafeVariance E>): PersistentCollection<E>
fun clear(): PersistentCollection<E>
/**
* 수정 연산용 builder
*/
interface Builder<E>: MutableCollection<E> {
fun build(): PersistentCollection<E>
}
fun builder(): Builder<@UnsafeVariance E>
}
전부 이름만 봐도 알 수 있는 함수이다. 특이한 점은 모든 수정 함수가 PersistentCollection
을 반환한다는 점. 말했다시피 원본 객체에 수정 연산을 적용한 사본을 반환하기 때문이다.
단순한 삽입/삭제보다 더 복잡한 연산을 하고 싶다면, PersistentCollection.mutate()
확장 함수를 사용하자. mutate()
함수 안에서는 현재 리스트의 사본을 MutableList
로 접근할 수 있어 다양한 수정 연산을 적용할 수 있다. 당연히 원본은 바뀌지 않는다.
// list의 타입은 MutableList이다.
persistentList.mutate { list -> list.replaceAll { number -> number * number } }
PersistentList
는 PersistentCollection
과 똑같은 형태의 인터페이스라 생략한다. 밑단의 세부적인 구현은 당연히 생략.
요약
- 데이터를 읽기만 한다면
Immutable...
을 사용하자. - 데이터를 수정한다면
Persistent...
를 사용하자.
Compose 개발자들은 stability 때문에라도 자주 사용할 듯하다.
'Primary > Kotlin' 카테고리의 다른 글
[Kotlin] Computed Property (0) | 2022.10.06 |
---|---|
[Kotlin] Coroutine 테스트 디버깅 후기 (0) | 2022.07.30 |
[Kotlin] sealed class vs. enum class (0) | 2022.07.07 |
[Kotlin] value class로 값을 감싸보자 (0) | 2022.06.17 |
[Kotlin] 클래스 안에 확장 함수? (0) | 2022.06.16 |