코틀린 & Java/코틀린인액션

[Kotlin] 람다에 대해 알아보자(2)

코딩하는후운 2023. 3. 23. 17:27
반응형

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는 항상 자신에게 전달된 객체를 반환

 

반응형