Table Of Contents
- 람다 소개
- 람다 - Collection
- 람다 문법
- 람다 안에서 외부 변수 접
- 멤버 참조
- 컬렉션 함수형 API (FILTER, MAP)
- 컬렉션 함수형 API (ALL, ANY, COUNT, FIND)
- 컬렉션 함수형 API (groupby, flatmap)
- LAZY 컬렉션 연산 (Sequence)
람다 소개
- 무명 내부 클래스 (JAVA)
- 코딩양이 너무 많다. 중복코드가 발생
- 코드가 번잡스럽다
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
}
})
- 람다 (Lambda)
- 클래스가 아닌 함수를 사용
- 불필요한 코드 제거 -> 가독성 향상
button.setOnClickListener{ it }
람다 - Collection
Java에서는 클래스 Comparator 를 제공했으나, Kotlin은 람다 함수 로 간략화
fun findTheOldest(people: List<Person>) {
var maxAge = 0
var theOldest: Person? = null
for(person in people) {
if (person.age > maxAge) {
maxAge = person.age
theOldest = person
}
}
println(theOldest)
}
람다 변환순서
- 원본 코드
val max: Person? = list.maxBy({p: Person -> p.age})
- 맨마지막 인자인 람다는 괄호 밖으로 이동 가능
val max: Person? = list.maxBy() {p: Person -> p.age}
- 유일한 인자가 람다이고, 괄호뒤에 람다라면 삭제가능
val max: Person? = list.maxBy {it.age}
람다 문법
- 람다는 중괄호로 둘러쌓여 있다.
- 화살표로 파라메타와 본문을 구분
- 람다호출에는 부가비용이 들지 않는다.
- 람다는 여러줄도 가능. 마지막 줄이 return
val sum: (int, int) -> int = { x: int, y: int ->
println("sum of $x and $y")
x + y
}
람다 안에서 외부 변수 접근
코틀린에서는 람다밖의 final이 아닌 변수 접근 및 수정가능
fun printProblemCounts(responses: Collection<String>) {
var clientErrors = 0
var serverErrors = 0
responses.forEach {
if (it.startsWith("4")) {
clientErrors++
} else if (it.startsWith("5")) {
serverErrors++
}
}
println("$clientErrors client errors, $serverErrors server errors") }
>>> val responses = listOf("200 OK", "418 I'm a teapot", ... "500 Internal Server Error")
>>> printProblemCounts(responses)
1 client errors, 1 server errors
멤버 참조
- 이미 함수로 선언된 것을 인자로 넘긴다
- 부가작업없이 단순히 다른함수한테 위임할때 함수참조가 편리
- 생성자 참조를 사용하여 클래스 생성 연기 및 저장가능
data class Person(val name:String, val age: Int)
>>> val createPerson = ::Person
>>> val p = createPerson("Alice", 29)
>>> print (p)
Person("Alice", 29)
- 확장함수 참조 가능
fun Person.isAdult() = age >= 21
val predicate = Person::isAdult
컬렉션 함수형 API (FILTER, MAP)
- filter, map - 필수적인 함수
- filter - 람다가 true를 반환하는 원소
>>> val list = listOf(1, 2, 3, 4)
>>> list.filter { it % 2 == 0 } [2, 4]
- map - 람다를 각원소에 적용한 결과 를 모아서 NEW collection을 만든다ㄹ
>>> val list = listOf(1, 2, 3, 4) >>> list.map { it * it }
[1, 4, 9, 16]
- filter와 map 연속사용
>>> val people = listOf(Person("Alice", 29), Person("Bob", 31))
>>> people.filter { it.age > 30 }.map(Person::name)
[Bob]
- 불필요한 계산 반복은 하지 말자 100명이면 maxBy를 100회 반복해야 하는 문제발생 (maxBy를 먼저 수행하여 불필요한 반복 방지)
people.filter { it.age == people.maxBy(Person::age).age. } (X)
val maxAge = people.maxBy(Person::age).age (O)
people.filter { it.age == maxAge }
- map타입은 따로 함수 제공
- filterKeys() / filterValue()
- mapKeys() / mapValues()
>>> val numbers = mapOf(0 to "zero", 1 to "one")
>>> println(numbers.mapValues { it.value.toUpperCase() }) {0=ZERO, 1=ONE}
컬렉션 함수형 API 2탄 (ALL, ANY, COUNT, FIND)
- all
val canBeInClub27 = { p: Person -> p.age <= 27 }
>>> val people = listOf(Person("Alice", 27), Person("Bob", 31)) >>> println(people.all(canBeInClub27))
false
- any
>>> println(people.any(canBeInClub27))
true
- count
>>> val people = listOf(Person("Alice", 27), Person("Bob", 31)) >>> println(people.count(canBeInClub27))
1
- find : lambda를 만족하는 원소 하나 찾기 (only one)
>>> val people = listOf(Person("Alice", 27), Person("Bob", 31)) >>> println(people.find(canBeInClub27))
Person(name=Alice, age=27)
컬렉션 함수형 API 3탄 (groupBy, flatmap)
- groupby : 특성에 따라 여러그룹으로 나누어 준다.
>>> val people = listOf(Person("Alice", 31), ... Person("Bob", 29), Person("Carol", 31))
>>> println(people.groupBy { it.age })
{29=[Person(name=Bob, age=29)], 31=[Person(name=Alice, age=31), Person(name=Carol, age=31)]}
>>> val list = listOf("a", "ab", "b")
>>> println(list.groupBy(String::first))
{a=[a, ab], b=[b]}
- flatmap (flat=결과를 한데 모은다, map=모든원소에 적용)
한마디로 설명하자면, 모든 원소에 하나씩 람다를 적용하고, 이를 다시 flat하게(list로) 만드는 것이다. flat하게 만드는 것은 결과가 여러개의 list인데, 이걸 한개의 List로 만든다는 것이다.
솔직히 flatmap이 제일 헛갈린다. 몇번을 보는건지 ㅠㅜ
>>> val strings = listOf("abc", "def")
>>> println(strings.flatMap { it.toList() }) // "abc".toList() = [a, b, c]
[a, b, c, d, e, f]
하나의 예제를 또 보자. 아래 코드는 3단계를 거친다.
- it.toList()를 이용하여 원소를 map처리 한다.
- [Jasper Fforde], [Terry Pratchett], [Terry Pratchett, Neil Gaiman]
- list원소를 flat하게 한개 list로 만든다.
- [asper Fforde, Terry Pratchett, Terry Pratchett, Neil Gaiman]
- 한개 list결과를 set처리 하여 중복 제거
- [asper Fforde, Terry Pratchett, Neil Gaiman]
class Book(val title: String, val authors: List)
>>> val books = listOf(Book("Thursday Next", listOf("Jasper Fforde")),
Book("Mort", listOf("Terry Pratchett")),
Book("Good Omens", listOf("Terry Pratchett", "Neil Gaiman")))
...
>>>
>>> println(books.flatMap { it.authors }.toSet())
[Jasper Fforde, Terry Pratchett, Neal Gaiman]
LAZY 컬렉션 연산 (Sequence)
- 연쇄호출시 여러개 리스트를 생성하는 문제. 수백만개일때 문제 발생
people
.map(Person::name)
.filter { it.startsWith("A") }
- sequence 중간처리결과를 저장안함. 원소 단위로 처리
people.asSequence()
.map(Person::name)
.filter { it.startsWith("A") }
.toList()
- 중간연산
- 다른 시퀀스를 반환 (최초 시퀀스의 원소를 반환하는 방법을 안다)
- 최종연산
- 최초 시퀀스부터 계산을 수행해 얻을수 있는 컬렉션이나 원소, 숫자 또는 객체
- 최종연산이 호출될때까지, map과 filter 변환이 미뤄진다.
>>> listOf(1, 2, 3, 4).asSequence()
.map { print("map($it) "); it * it }
.filter { print("filter($it) "); it % 2 == 0 }
> 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)