* 람다 식과 멤버 참조
* 함수형 스타일로 컬렉션 다루기
* 시퀀스: 지연 컬렉션 연산
* 자바 함수형 인터페이스를 코틀린에서 사용
* 수신 객체 지정 람다 사용
이벤트 발생시 핸들러 실행
, 컬렉션에서 모든 원소에 특정 연산을 적용하자
와 같은 생각을 코드로 표현하기 위해 무명 내부 클래스를 사용하여 해결해왔다. 하지만 상당히 번거롭다.
이와 달리 람다 프로그래밍에서는 함수를 값처럼 다루는 접근 방법을 택함으로 이 문제를 해결한다.
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {/* 클릭시 수행할 동작 */}
});
위 코드를 보면 무명 내부클래스로 리스너의 동작을 구현하느라 코드가 번잡해졌다. 이를 람다식으로 바꿔보자.
button.setOnClickListener { /* 클릭 시 수행할 동작 */ }
컬렉션을 다룰 때 수행하는 대부분의 작업은 몇 가지 일반적인 패턴에 속한다. 해당 패턴은 컬렉션 라이브러리에서 제공해야 하지만 람다가 없다면 해당 패턴을 제공하기 어렵다. 코틀린에서는 람다를 이용하여 편리한 패턴을 제공한다. (물론 현재 자바 8에서 제공해주긴 한다)
람다를 사용해본 적이 없는 개발자에게 사람 리스트에서 가장 나이가 많은 사람을 검색하는 미션
이 주어졌다.
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)
}
순식간에 위와 같은 코드를 짜서 미션을 수행했다. 하지만, 코드가 길고 자칫하면 실수를 저지를 여지가 충분하다. 코틀린에서는 더 좋은 방법이 있다. 라이브러리 함수를 사용해보자.
println(people.maxBy { it.age })
대단하다! 중괄호에 둘ㄹ러싸인 코드 { it.age }
는 maxBy
함수에서 최댓값을 뽑는 기준이 무엇인지 인자를 가리킨다.
people.maxBy(Person::age)
이 코드도 위와 같은 역할을 한다.
filter함수
는 컬렉션을 이터레이션하면서 주어진 람다에 각 원소를 넘겨서 람다가 true를 반환하는 원소만 모으는 함수이다.
val list = listOf(1, 2, 3, 4)
println(list.filter { it % 2 == 0 }
[2, 4]
위와 같이 filter함수
는 컬렉션에서 조건에 맞지 않는 원소를 제거한다. 하지만 filter
는 원소를 변환할 수는 없다. 이때 사용하는 것이 map함수
이다. map함수
는 주어진 람다를 컬렉션의 각 원소에 적용한 결과를 모아서 새 컬렉션을 만든다.
val list = listOf(1, 2, 3, 4)
println(list.map { it * it }
[1, 4, 9, 16]
User
(이름과 나이를 포함)객체 리스트에서 User
를 출력하는 것이 아닌 이름을 출력하고 싶다면 map
으로 이름의 리스트로 변환하면 된다.
val people = listOf(Person("Alice", 29), Person("Bob", 31))
println(people.map { it.name })
[Alice, Bob]
연쇄 호출하여 편리하게 코드를 작성할 수 있다. 예시로 30살 이상인 사람의 이름을 출력해보자.
people.filter { it.age >= 30 }.map(it.name)
filter와 map은 맵에서도 사용가능하다. → filterKeys, mapKeys, filterValues, mapValues
컬렉션의 모든 원소가 어떤 조건을 만족하는지 판단하는 연산이 있다. 바로 all
과 any
이다.
val people = listOf(Person("Alice", 29), Person("Bob", 31))
println(people.all { it.age > 30 }) -> 모든 원소가 만족 X 이므로 false
println(people.any { it.age > 30 }) -> 만족하는 원소가 존재하므로 true
조건을 만족하는 원소를 하나 찾고 싶으면 find
함수를 사용한다. find
함수는 조건을 만족하는 첫 번째 원소를 반환한다.(firstOrNull
과 같은 역할을 하는 함수다.) 참고로 조건을 만족하는 마지막 원소를 반환하는 함수는 findLast
이다.
조건을 만족하는 원소의 개수를 구하려면 count
함수를 사용한다. (참고로 size
를 사용하지 마라 - 중간 컬렉션이 한번 더 생기므로 성능에 좋지 않다.)
컬렉션을 여러 그룹으로 구분 지을 "키"를 선정하여 키 값에 따른 각 그룹이 "값"이 되도록 map
을 만든다.
val people = listOf(
Person("Alice", 28),
Person("Bob", 31),
Person("Brown", 32),
Person("Kotlin", 28)
)
var map = people.groupBy { it.age }
위와 같이 groupBy
로 묶은 경우 it.age
가 키값이 되고 해당하는 값이 Person
객체가 된다. 그리고 값은 여러 개 있을 수 있으므로 기본적으로 List<T>
타입이 된다. 즉 여기서는 List<Person>
이다.
flatMap
은 먼저 인자로 주어진 람다를 컬렉션의 모든 객체에 적용하고 그 결과로 얻어지는 여러 리스트들을 한 리스트로 한 데 모은다.
flatMap
을 이용하여 각 리스트에 있는 원소들을 평평하게 만들어 하나의 리스트로 만들 수 있다.
val corpList = listOf(
Corp("Samsung", listOf("삼성전자", "삼성SDS", "삼성SDI", "삼성물산", "삼성화재")),
Corp("LINE", listOf("라인플러스", "라인비즈플러스", "라인파이낸셜플러스"))
)
println(corpList.flatMap { it.sonCorpList })
// result
[삼성전자, 삼성SDS, 삼성SDI, 삼성물산, 삼성화재, 라인플러스, 라인비즈플러스, 라인파이낸셜플러스]
각 리스트에서 it.sonCorpList
라는 리스트의 원소들을 하나의 리스트로 평평하게 만들었다.
flatten
은 중첩된 리스트의 원소를 한 리스트로 모으고 싶을 때 사용한다. ???
flatMap
하고 무슨차이지? 라고 반문할 수도 있겠다. 나 또한 역시 책에서 해당 글을 보고 차이를 이해하지 못했다.
flatten은 람다식이 없는 경우 사용한다. 즉, 위의 코드에서 적용하지 못한다. 이것이 가장 큰 차이일 수 있겠다. 위의 코드에서는 it.sonCorpList
라는 람다식이 필요하다.
하지만, 람다식이 필요없을 때도 있다. 이 때 flatten을 사용하게 되는데 아래의 경우를 보자.
val deepArray = arrayOf(
arrayOf(1),
arrayOf(2, 3),
arrayOf(4, 5, 6)
)
println(deepArray.flatten()) // [1, 2, 3, 4, 5, 6]
이 경우에는 순수한 리스트 of 리스트
형태이다 이때는 굳이 람다식이 필요가 없기 때문에 (flatMap
으로도 가능하지만 굳이 그럴 필요없이)flatten
함수로 평평하게 만든다.