프로그래밍에서 컬렉션은 가장 중요한 개념 중 하나일 것이다. 코틀린도 컬렉션 처리 과정을 위한 여러가지 도구를 제공해준다.
val visibleNews = mutableListOf<News>()
for(n in news){
if(n.visible) {
visibleNews.add(n)
}
}
Collections.sort(visibleNews,{ n1, n2 -> n2.publishedAt - n1.publishedAt })
val newsItemAdapters = mutableListOf<NewsItemAdapter>()
for(ninvisibleNews){
newsItemAdapters.add(NewsItemAdapter(n))
}
위의 코드를 코틀린에서는 아래와 같이 짤 수 있다.
val newItemsAdapters = news
.filter { it.visibile }
.sortedByDescending { it.publishedAt }
.map(::NewsItemAdapter)
코드가 간결해지고 그리고 가독성까지 더 좋아졌다!
컬렉션 처리과정을 최적화 하는것은 매우 중요하다. 특히, 데이터 분석가나 백앤드 개발자라면 이를 무시하고 넘어가서는 안된다. 이러한 최적화는 어려운 것이 아니라 몇가지만 기억하면 된다. 이 방법들을 살펴보자.
Item 49 : 처리 단계가 두개 이상인 대규모 컬렉션에서는 sequence를 더 선호하라
인터페이스만 보면 두개는 별 차이가 없어 보인다.
interface Iterable<out T> {
operator fun iterator() : Iterator<T>
}
interface Sequence<out T> {
operator fun iterator() : Iterator<T>
}
하지만 중요한 차이점이 있다
- Iterable processing : 매 단계마다 List와 같은 컬렉션을 반환한다
- Sequence processing : lazy, 새로운 동작과 함께 decorate된 새로운 Sequence를 반환한다
public inline fun <T> Iterable<T>.filter(
predicate: (T) -> Boolean
): List<T> {
return filterTo(ArrayList<T>(), predicate)
}
public fun <T> Sequence<T>.filter(
predicate: (T) -> Boolean
): Sequence<T> {
return FilteringSequence(this, true, predicate)
}
그래서 이러한 sequence의 lazy한 특성이 어떠한 좀이 좋은것 일까?
Order is important
- sequence processing : 원소 원소 하나씩 모든 프로세스를 태운다 ( element-by-element or lazy order )
- iterable processing : 프로세스 하나하나를 모든 원소에 태운다 ( step-by-step or eager order )
Sequences do the minial number of operations
종종 우리는 결과를 생산하기 위해 모든 단계에서 전체 컬렉션을 처리할 필요가 없을 때도 있다. 아래 그림을 보면 왜 더 효율적인지 이해가 된다.
Sequences do not create collections at every processing step
대략 700MB인 파일을 라인별로 읽어서 filter 한 후 출력을 한다면?
- iterator : 13초
- sequence : 4초, 메모리도 더 적게 먹음
5000개의 element중 구매한 상품의 가격 평균을 구하는 로직?
- list processing : 81ns
- sequence processing : 55 ns
- list processing and acumulate ( average() ) : 83 ns
- sequence processing and acumulate : 6 ns
Sequence를 써야 할 합당한 이유 ( 큰 컬렉션 작업 )가 있고 적절하게 사용한다면 성능의 이점을 얻을 것이다
Item 50 : operation의 횟수를 최대한 줄여라
class Student(val name : String?)
//Works
fun List<Student>.getNames() : List<String> =this .map { it.name }
.filter { it != null }
.map { it!! }
//Better
fun List<Student>.getNames():List<String>=this.map { it.name }
.filterNotNull()
//Best
fun List<Student>.getNames():List<String>=this.mapNotNull { it.name }
아래는 operation의 횟수를 줄이기 위한 몇몇의 가이드이다.
위의 가이드를 통해 컬렉션이 iterate 되는 동안 중간 컬렉션 생성 비용을 줄여줄 수 있다.
Item 52 : mutuable collection 사용을 고려하라
mutable collection의 장점 중 하나는 훨씬 빠르다는 것이다. immutable collection에 하나의 원소를 더할 때, 우린 새로운 collection을 생성한 후에 하나의 원소를 더해야 한다.
operator fun <T> Iterable<T>.plus(element: T) : List<T> {
if (this is Collection) return this.plus(element)
val result = ArrayList<T>()
result.addAll(this)
result.add(element)
return result
}
그렇다고 무조건 mutuable을 사용하는게 좋을까? immutable을 사용하면 그들이 어떻게 변하는지를 우리가 제어할 수 있다는 장점이 있다. 그렇기 때문에, local scope 정도에서 원소 추가 작업이 많은 경우정도에 mutable 사용을 권장한다.
'Kotlin' 카테고리의 다른 글
kotlin Coroutine 쉽게 이해하기 2 - Context와 Dispatchers (0) | 2021.03.09 |
---|---|
kotlin Coroutine 쉽게 이해하기 1 - 기본 (0) | 2021.03.09 |
Kotlin Effectvie 번역 - 2. Readability (0) | 2020.04.20 |
Kotlin in Action - 1장. 코틀린이란? 왜 필요한가? (0) | 2020.04.04 |
Kotlin in Action 11장 : DSL 만들기 (0) | 2020.04.03 |