Kotlin의 var List vs val MutableList

Hyemdooly·2023년 2월 27일
1

List vs MutableList

Kotlin의 List와 MutableList의 차이점을 알아보고 어떤 경우에 사용하면 좋은지 알아보자!

간단하게 짚고 가기

List

불변 - 만든 후에 요소가 바뀔 수 없음, 대신 sorted(), reversed() 와 같은 새 List를 만들어 반환하는 함수 사용 가능

MutableList

가변 - 만든 후에 요소가 바뀔 수 있음 → add(), remove(), … 요소를 변경하는 함수 사용 가능

List vs MutableList 무엇을 사용하면 좋을까?

var list1 = listOf(1, 2, 3, 4, 5, 6)
val list2 = mutableListOf(1, 2, 3, 4, 5, 6)

두 형태 모두 요소를 바꿀 수 있는 형태이다.

var list1 = listOf(1,2,3,4,5,6)
list1 = list1 + listOf(7) // 1, 2, 3, 4, 5, 6, 7

val list2 = mutableListOf(1, 2, 3, 4, 5, 6)
list2.add(7) // 1, 2, 3, 4, 5, 6, 7

list1은 +연산을 사용하여 새 List를 만들어 대입, list2는 MutableList의 함수인 add를 활용한 모습이다.

list1은 요소가 변하지 않을 것이라는 데이터의 불변성을 보장할 수 있지만 list2는 그렇지 않다. 하지만 요소를 변경하는 함수가 지원되므로 편리하게, 직관적으로 요소를 변경하는 코드를 작성할 수 있다.

그래도 List를 사용할 때 데이터의 불변성이 보장되는게 좋은게 아닌가? 라고 생각할 수 있지만…

Effective Kotlin Item 56: Consider using mutable collections

위 문서의 첫 문장에 나와있듯, MutableList는 강한 성능을 보여준다고 한다. 정말일까?

import kotlin.time.ExperimentalTime
import kotlin.time.TimeSource

@OptIn(ExperimentalTime::class)
fun main() {
    var mark = TimeSource.Monotonic.markNow()
    var list1 = listOf(1, 2, 3, 4, 5, 6)
    list1 = list1+listOf(7) // --- (1)
    println(list1)
    println(mark.elapsedNow())

    mark = TimeSource.Monotonic.markNow()
    val list2 = mutableListOf(1, 2, 3, 4, 5, 6)
    list2.add(7) // --- (2)
    println(list2)
    println(mark.elapsedNow())
}

먼저 시간을 재보았다.

(1)보다 (2)가 확연히 더 빠르다는 것을 확인할 수 있었다. 디컴파일도 해보자

public static final void main() {
      long mark = Monotonic.INSTANCE.markNow-z9LOYto();
      List list1 = CollectionsKt.listOf(new Integer[]{1, 2, 3, 4, 5, 6});
      list1 = CollectionsKt.plus((Collection)list1, (Iterable)CollectionsKt.listOf(7));
      System.out.println(list1);
      Duration var3 = Duration.box-impl(ValueTimeMark.elapsedNow-UwyO8pc(mark));
      System.out.println(var3);
      mark = Monotonic.INSTANCE.markNow-z9LOYto();
      List list2 = CollectionsKt.mutableListOf(new Integer[]{1, 2, 3, 4, 5, 6});
      list2.add(7);
      System.out.println(list2);
      Duration var4 = Duration.box-impl(ValueTimeMark.elapsedNow-UwyO8pc(mark));
      System.out.println(var4);
   }

CollecitonsKt.plus() 를 사용하는구나! 더 파고 들어가보자

public operator fun <T> Collection<T>.plus(elements: Iterable<T>): List<T> {
    if (elements is Collection) {
        val result = ArrayList<T>(this.size + elements.size)
        result.addAll(this)
        result.addAll(elements)
        return result
    } else {
        val result = ArrayList<T>(this)
        result.addAll(elements)
        return result
    }
}

새 List를 만들어서 반환하는 모습을 확인할 수 있었다. (1)의 방법이 더 느리다는 것을 알 수 있었다.

결론

var list1 = listOf(1,2,3,4,5,6)
list1 = list1 + listOf(7) // 1, 2, 3, 4, 5, 6, 7

val list2 = mutableListOf(1, 2, 3, 4, 5, 6)
list2.add(7) // 1, 2, 3, 4, 5, 6, 7

첫 번째의 경우에는 느리지만, 변할 수 없는 List를 반환받아 사용하므로 이러한 경우를 클래스에 적용했을 때 데이터가 외부에서 변환되지 않는다는 보장이 있다.

두 번째의 경우 첫 번째 처럼 데이터 변환에 대한 보장은 없지만 빠르게 add할 수있다.

따라서, 나는 List를 자주 변경해야한다면 두 번째 방법을, 그렇지 않다면 첫 번째 방법이 좋다고 생각한다.

물론 다른 의견도 있다!

아래 스택오버플로우 글에서는 성능 문제가 아니더라도 Collection이 중간에 수정될 여지가 있음을 직관적으로 나타낼 수 있는 2번을 선호한다는 의견도 존재했다.

Kotlin: val mutableList vs var immutableList. When to use which?

요약하자면, Collection이 바뀔 수 있다면 mutable을 사용하고, 오직 보기 전용이라면 immutable를 사용한다. mutable vs immutable과 별개로 val과 var의 목적은 값이나 주소가 바뀔 수 있고 없고의 관점이기 때문에 var을 사용하게 되면 ‘이 변수가 나중에 바뀔 수 있구나!’를 직관적으로 확인할 수 있는 것이다.

0개의 댓글