5-3 지연 계산(lazy) 컬렉션 연산
map,filter와 같은 컬렉션은 결과 컬렉션을 즉시(eagerly) 생성
시퀀스(sequence)를 사용하면 중간 임시 컬렉션을 사용하지않고도 컬렉션 연산을 연쇄
WHAT?????
/**
* Returns a list containing only elements matching the given [predicate].
*
* @sample samples.collections.Collections.Filtering.filter
*/
public inline fun <T> Iterable<T>.filter(predicate: (T) -> Boolean): List<T> {
return filterTo(ArrayList<T>(), predicate)
}
/**
* Returns a list containing the results of applying the given [transform] function
* to each entry in the original map.
*
* @sample samples.collections.Maps.Transformations.mapToList
*/
public inline fun <K, V, R> Map<out K, V>.map(transform: (Map.Entry<K, V>) -> R): List<R> {
return mapTo(ArrayList<R>(size), transform)
}
코틀린 표준 라이브러리 참조 문서에는 filter와 map이 리스트를 반환한다고 써있음
people.map(Person::name).filter { it.startsWith("A") }
filter, map이 리스트를 반환 → 이 연쇄 호출이 리스트를 2개만든다→ 원소의 갯수가 늘어날 수록 효율이 떨어짐
→ 각 연산이 컬렉션을 직접 사용하는 대신 시퀀스를 사용하게 만들어야함
NOTE. 큰 컬렉션에 대해서 연산을 연쇄시킬 때는 시퀀스를 사용하는것을 규칙으로 삼아라
시퀀스에 대한 연산은 중간(intermediate)연산과 최종(terminal)연산으로 나뉨
>>> listOf(1, 2, 3, 4).asSequence()
.map { print("map($it) "); it * it }
.filter { print("filter($it) "); it % 2 == 0}
이 코드를 실행하면 아무 내용도 출력되지 않음.
map과 filter변환이 늦춰져서 결과를 얻을 필요가 있을 때(즉 최종 연산이 호출될 때) 적용된다는 뜻
>>> listOf(1, 2, 3, 4).asSequence()
.map { print("map($it) "); it * it }
.filter { print("filter($it) "); it % 2 == 0}
.toList()
map(1) filter(1) map(2) filter(4) map(3) filter(9) map(4) filter(16)
각 숫자를 제곱하고 3보다 큰 첫번째 수를 찾아보자
컬렉션을 사용하면 리스트가 다른 리스트로 변환됨
시퀀스를 사용하면 find 호출이 원소를 하나씩 처리
💡 sequence
- 데이터를 필요할때마다 계산하기 때문에 데이터를 많이 다룰 수 있음
- 대신 데이터를 필요할때마다마다 계산해내기 때문에 데이터 전체의 길이를 구하는 것은 불가능. 따라서 size나 length같은 길이 관련 프로퍼티를 사용할 수 없음
컬렉션에 대해 수행하는 연산의 순서도 성능에 영향
val people = listOf(Person("Alice", 29), Person("Bob", 31), ... , Person("Dan", 21))
println(people.asSequence().map(Person::name).filter {it.name.length < 4}.toList())
[Bob, Dan]
println(people.asSequence().filter {it.name.length < 4}.map(Person::name).toList())
[Bob, Dan]
시퀀스 만들기
val naturalNumbers = generateSequence(0) { it + 1 }
val numbersTo100 = naturalNumbers.takeWhile { it <= 100 }
println(numbersTo100.sum()) //모든 지연 연산은 "sum"의 결과를 계산할 때 수행
>>>5050
참고) https://developer88.tistory.com/m/182
5-4 자바 함수형 인터페이스 활용
button.setOnClickListener { /* 클릭 시 수행할 동작*/ } // <- 람다를 인자로 넘김
public interface OnClickListener {
void onClick(View v); // -> { view -> ...}
}
//람다의 파라미터는 메서드의 파라미터와 대응한다.
이런 코드가 작동하는 이유는 onClickListener에 추상 메서드가 단 하나만 있기 때문
→ 함수형 인터페이스(functional interface) 또는 SAM(단일 추상 메서드 single abstract method) 인터페이스
자바 메서드에 람다를 인자로 전달
void postponeComputation(int delay, Runnable computation);
컴파일러는 자동으로 람다를 Runnable 인스턴스로 변환
postponeComputation(1000) { println(42) }
Runnable을 구현하는 무명 객체를 명시적으로 만들어서 사용할 수도있음
postponeComputation(1000, object : Runnable { //<- 객체 식을 함수형 인터페이스 구현으로 넘김
override fun run() {
println(42)
}
})
이때 메서드를 호출할 때마다 새로운 객체가 생성됨
반면 람다는
postponeComputation(1000) { println(42) }
프로그램 전체에서 Runnable의 인스턴스는 단 하나만 만들어짐
val runnable = Runnable {println(42)} //Runnable은 SAM 생성자
//<- 전역 변수로 컴파일되므로 프로그램 안에 단 하나의 인스턴스만 존재한다.
fun handleComputation() {
postponeComputation(1000, runnable )//<- 모든 handleComputation 호출에 같은 객체를 사용
}
SAM 생성자: 람다를 함수형 인터페이스로 명시적 변경
컴파일러가 자동으로 람다를 함수형 인터페이스 무명 클래스로 바꾸지 못하는 경우 SAM 생성자 사용
fun createAllDoneRunnable() : Runnable {
return Runnable { println("All done!") }
}
>>> createAllDoneRunnable().run()
All done!
람다로 생성한 함수형 인터페이스 인스턴스를 변수에 저장해야 하는 경우에도 SAM 생성자를 사용
val listener = OnClickListener { view ->
val text = when (view.id) { //<- view.id를 사용해 어떤 버튼이 클릭됐는지 판단
R.id.button1 -> "First button"
R.id.button2 -> "Second button"
else -> "Unknown button"
}
toast(text) //<- text의 값을 사용자에게 보여줌
}
button1.setOnClickListener(listener)
button2.setOnClickListener(listener)
람다에는 무명 객체와 달리 인스턴스 자신을 가리키는 this가 없다. 람다 안에서 this는 그 람다를 둘러싼 클래스의 인스턴스를 가리킨다.
이벤트 리스너가 이벤트를 처리하다가 자기 자신의 리스너 등록을 해제해야 한다면 람다를 사용 할 수 없다. 이 경우 람다 대신 무명 객체를 사용해 리스너를 구현하라
5.5 수신 객체 지정 람다 : with와 apply
수신 객체를 명시하지 않고 람다의 본문 안에서 다른 객체의 메서드를 호출할 수 있게하는것
with 함수
inline fun <T, R> with(receiver: T, block: T.() -> R): R {
return receiver.block()
}
정의에서 receiver 가 수신 객체 , block 이 수신 객체 지정 람다
람다의 결과 대신 수신객체가 필요한 경우 apply 사용
apply 함수
inline fun <T> T.apply(block: T.() -> Unit): T {
block()
return this
}
apply는 항상 자신에게 전달된 객체를 반환
'코틀린 & Java > 코틀린인액션' 카테고리의 다른 글
Kotlin Plus 산술연산자 오버로딩 (0) | 2023.05.09 |
---|---|
코틀린 원시타입(Kotlin Primitive Type) (0) | 2023.04.06 |
코틀린 타입 종류 Null가능성 (0) | 2023.03.27 |
[Kotlin] 스코프 펑션(Scoepe Function) (0) | 2023.03.23 |
[Kotlin] 람다에 대해 알아보자 (0) | 2023.03.23 |