버튼 수집상

[Kotlin] List.map 파헤치기 본문

TIL - Kotlin

[Kotlin] List.map 파헤치기

cocokaribou 2023. 7. 20. 16:35

얼마 전에 ListAdapter의 DiffUtil을 쓰는 법에 대한 글을 썼었다.

 

[안드로이드] ListAdapter DiffUtil 제대로 쓰기 - 1

배경 리사이클러뷰에서 부분적으로 UI를 업데이트할 때 (ex: 찜하기) 업데이트한 리스트를 submitList()로 세팅해도 DiffUtil이 제대로 돌아가지 않는 경우가 있었다. 리스트 변경사항을 제대로 감지하

collectingbuttons.tistory.com

글 1편은 ListAdapter 보다 List.map()에 대해 적었다.

그런데 위 글을 적은 후에도 여전히 헷갈리는 부분이 있어서 다시 정리해봤다.

 

코틀린 Collections 파일에서 map 함수의 시그니처를 보자.

/**
 * Returns a list containing the results of applying the given [transform] function
 * to each element in the original collection.
 * 
 * @sample samples.collections.Collections.Transformations.map
 */
public inline fun <T, R> Iterable<T>.map(transform: (T) -> R): List<R> { /** 구현부 생략 **/ }

map은 T타입의 Iterable 객체를 받아서 R타입 리스트로 리턴한다.
인자로 받아온 Transform 함수를 람다식으로 표현한다.

var testList : List<Int> = listOf(1,2,3)
var newList : List<String> = testList.map { it -> "${it*100}"}
    
println(testList)
println(newList)

// [1, 2, 3]
// [100, 200, 300]

위 예시에서
람다식의 it 이 Transform 함수의 인자, T타입 객체이고

람다식의 "${it*100}" 이 Tranform 함수의 리턴값, R타입 객체다.

map 블럭의 마지막 줄에 리턴값 적는 것을 잊지 않아야 한다.

안 그러면 Unit을 반환한다.

 

map 블럭 안에서 원본 요소를 변경하려고 하면 에러가 뜬다.

var testList : List<Int> = listOf(1,2,3)

// Error : Val cannot be reassigned
var newList : List<Int> = testList.map { 
    it *= 100 
    it
}

String, Int와 같은 Primitive 타입 데이터는 한 번 생성되면 값을 변경할 수 없기 때문이다. (Immutable)

코드 돌려보기

 

그렇다면 오브젝트 타입 리스트를 만들어보자.

data class SimpleObject(
    var name: String,
    var isChecked: Boolean = false
){
    override fun toString(): String {
        return "$name ($isChecked)"
    }
}

fun main() {
    var testList : List<SimpleObject> = listOf(
        SimpleObject("Obj1"), 
        SimpleObject("Obj2")
    )
    
    var newList : List<SimpleObject> = testList.map { 
        it.isChecked = true
        it
    }
    
    println(testList)
    println(newList)
}

// [Obj1 (true), Obj2 (true)]
// [Obj1 (true), Obj2 (true)]

SimpleObjectisChecked는 값을 변경할 수 있다. (Mutable var)

그래서 람다식 안에서 원본 요소에 직접 접근해서 값을 수정했더니, 원본 리스트도 변경됐다.

리스트의 요소의 객체가 바뀌지 않았기 때문에 얕은 복사가 된 것이다.

 

객체 주소값을 비교할 땐 equals()나 hashCode()을 사용한다.

코드 돌려보기

 

원본 리스트에 영향받지 않으려면 map 새로운 객체를 생성해서 리턴한다.

fun main() {
    var testList : List<SimpleObject> = listOf(
        SimpleObject("Obj1"), 
        SimpleObject("Obj2")
    )
    
    var newList : List<SimpleObject> = testList.map { 
        it.copy().apply { isChecked = true }
    }
    
    println(testList)
    println(newList)
}

// [Obj1 (false), Obj2 (false)]
// [Obj1 (true), Obj2 (true)]

코드 돌려보기

 

 

결론

Primitive 타입 리스트는 map 블럭 안에서 요소의 값을 변경할 수 없다. 고로 원본 리스트가 변경될 여지가 없다.

참조타입 리스트는 map 블럭 안에서 요소 객체를 새로 생성하지 않고 값을 변경하면 원본도 같이 변경된다.

 

728x90