람다를 메소드가 하나뿐인 무명 객체 대신 사용할 수 있음
fun main(args: Array<String>) {
/* 자바 - 무명 내부 클래스로 리스너 구현
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
클릭 시 수행할 동작
}
});
*/
/*
button.setOnClickListener { 클릭 시 수행할 동작 }
*/
}
data class Person(val name: String, val age: Int) // 사람의 이름과 나이를 저장하는 클래스
// 컬렉션을 직접 검색하기
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)
}
fun main(args: Array<String>) {
val people = listOf(Person("Alice", 29), Person("Bob", 31))
// 컬렉션을 직접 검색
findTheOldest(people) // Person(name=Bob, age=31)
// 람다를 사용해 컬렉션 검색
println(people.maxByOrNull { it.age }) // 나이 프로퍼티를 비교해서 값이 가장 큰 원소 찾기
// 멤버 참조를 사용해 컬렉션 검색
println(people.maxByOrNull(Person::age))
}
fun main(args: Array<String>) {
val sum = { x: Int, y: Int -> x + y }
println(sum(1, 2)) // 변수에 저장된 람다를 호출
run {println(42)} // 람다 본문에 있는 코드를 실행, run: 인자로 받은 람다를 실행해주는 라이브러리 함수
// 여러줄로 이루어진 람다 - 본문의 맨 마지막에 있는 식이 결과 값이 됨
val sum2 = { x: Int, y: Int ->
println("Computing the sum of $x and $y...")
x + y
}
println(sum2(1, 2))
// Computing the sum of 1 and 2...
// 3
}
package ch05
data class Person(val name: String, val age: Int) // 사람의 이름과 나이를 저장하는 클래스
// 컬렉션을 직접 검색하기
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)
}
fun main(args: Array<String>) {
val people = listOf(Person("Alice", 29), Person("Bob", 31))
// 컬렉션을 직접 검색
findTheOldest(people) // Person(name=Bob, age=31)
// 멤버 참조를 사용해 컬렉션 검색
println(people.maxByOrNull(Person::age))
// 람다를 사용해 컬렉션 검색
println(people.maxByOrNull { it.age }) // 나이 프로퍼티를 비교해서 값이 가장 큰 원소 찾기
// (1) 정식으로 람다 작성
println(people.maxByOrNull({ p: Person -> p.age }))
// (2)
// 마지막에 있는 인자가 람다 식이므로 괄호 밖으로 빼냄
println(people.maxByOrNull() { p: Person -> p.age })
// (3)
// 람다가 어떤 함수의 유일한 인자이고, 괄호 뒤에 람다를 썼다면 호출 시 빈 괄호 없애도 됨
println(people.maxByOrNull { p: Person -> p.age })
// (4)
// 파라미터 타입을 생략(컴파일러가 추론)
println(people.maxByOrNull { p -> p.age })
// (5)
// 디폴트 파라미터 이름 it 사용하기
println(people.maxByOrNull { it.age })
// (6) 람다를 변수에 저장할 때는 파라미터 타입 명시 필수(추론할 문맥 존재x)
val getAge = {p: Person -> p.age }
println(people.maxByOrNull(getAge))
/*
// 이름 붙인 인자를 사용해 람다 넘기기
val people2 = listOf(Person("이몽룡", 29), Person("성춘향", 31))
val names = people2.joinToString(separator = " ", transform = { p: Person -> p.name})
println(names) // 이몽룡 성춘향
*/
}
람다를 함수 안에서 정의 -> 함수의 파라미터, 로컬 변수까지 모두 사용 가능
포획한 변수 : 람다 안에서 사용하는 외부 변수
멤버 참조 : '::'를 사용하는 식
people.maxBy(Person::age)
people.maxBy { p -> p.age }
people.maxBy { it.age }
fun salute() = println("Salute!")
run(::salute) // Salute!
// 최상위 함수
fun salute() = println("Salute!")
fun sendEmail(person: Person, message: String) = println("sendEmail")
fun Person.isAdult() = age >= 21 // 확장 함수
fun main(args: Array<String>) {
run(::salute) // 최상위 함수 참조
// 작업을 위임하는 함수에 대한 참조
val action = { person: Person, message: String -> sendEmail(person, message)} // 람다로 위임
val nextAction = ::sendEmail // 람다 대신 멤버 참조 사용
// 생성자 참조 -> 클래스 생성 작업 연기, 저장
val createPerson = ::Person // "Person"의 인스턴스를 만드는 동작을 값으로 저장
val p = createPerson("Alice", 29)
println(p) // Person(name=Alice, age=29)
// 확장 함수 참조
val predicate = Person::isAdult
println(predicate(p)) // true
}
filter : 컬렉션을 이터레이션하면서 주어진 람다에 각 원소를 넘겨서 람다가 true를 반환하는 원소만 모음 (맵에 적용 - filterKeys, filterValues)
map : 주어진 람다를 컬렉션의 각 원소에 적용한 결과를 모아서 새 컬렉션을 만듦 (맵에 적용 - mapKeys, mapValues)
data class Person2(val name: String, val age: Int)
fun main(args: Array<String>) {
val list = listOf(2, 3, 4, 5)
println(list.filter { it % 2 == 0}) // 짝수만 남음 [2, 4]
val people = listOf(Person2("Alice", 29), Person2("Bob", 31))
println(people.filter{ it.age > 30 }) // [Person(name=Bob, age=31)]
// 각 숫자의 제곱이 모인 리스트로 바꾸기
val list2 = listOf(1, 2, 3, 4)
println(list2.map {it * it}) // [1, 4, 9, 16]
// 사람 리스트를 이름 리스트로 변환
println(people.map { it.name })
println(people.map(Person2::name)) // 멤버 참조를 사용
// 호출 연쇄 시키기
println(people.filter {it.age > 30}.map(Person2::name)) // [Bob]
// 가장 나이 많은 사람의 이름 목록
val maxAge = people.maxByOrNull(Person2::age)!!.age
println(people.filter {it.age == maxAge }) // [Person2(name=Bob, age=31)]
// 필터와 변환 함수 맵에 적용
val numbers = mapOf(0 to "zero", 1 to "one")
println(numbers.mapValues { it.value.toUpperCase() }) // {0=ZERO, 1=ONE}
}
data class Person2(val name: String, val age: Int)
fun main(args: Array<String>) {
//==== 5.2.2 all, any, count, find ====//
val canBeInClub27 = { p: Person2 -> p.age <= 27 } // 어떤 사람의 나이가 27살 이하인지 판단하는 함수
val people2 = listOf(Person2("Alice", 27), Person2("Bob", 31))
// (1) all 함수 : 모든 원소가 술어를 만족하는지 판단
println(people2.all(canBeInClub27)) // false
// (2) any 함수 : 술어를 만족하는 원소가 하나라도 있는지 판단
println(people2.any(canBeInClub27)) // true
// (3) count 함수 : 술어를 만족하는 원소의 개수 구함
println(people2.count(canBeInClub27)) // 1
// (4) find 함수 : 술어를 만족하는 원소 하나 찾기 == firstOfNull과 같음
println(people2.find(canBeInClub27)) // Person2(name=Alice, age=27)
}
groupBy : 연산의 결과는 컬렉션의 원소를 구분하는 특성이 키, 키 값에 따른 각 그룹이 값인 맵
package ch05
data class Person2(val name: String, val age: Int)
fun main(args: Array<String>) {
//==== 5.2.3 groupBy ====//
val people3 = listOf(Person2("Alice", 31), Person2("Bob", 29), Person2("Carol", 31))
println(people3.groupBy {it.age}) // {31=[Person2(name=Alice, age=31), Person2(name=Carol, age=31)], 29=[Person2(name=Bob, age=29)]}
// 멤버 참조를 활용해 문자열을 첫 글자에 따라 분류
val list3 = listOf("a", "ab", "b")
println(list3.groupBy(String::first)) // String의 확장 함수 first
// 결과 : {a=[a, ab], b=[b]}
}
flatMap : 인자로 주어진 람다를 컬렉션의 모든 객체에 적용(또는 매핑) -> 람다를 적용한 결과 얻어지는 여러 리스트를 한 리스트로 한데 모음(또는 펼치기)
flatten : 변환해야 할 내용이 없다면 리스트의 리스트를 평평하게 펼치기만 할 때 사용
package ch05
class Book(val title: String, val authors: List<String>)
fun main(args: Array<String>) {
//==== 5.2.4 flatMap과 flatten ====//
val strings = listOf("abc", "def")
println(strings.flatMap { it.toList() }) // [a, b, c, d, e, f]
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, Neil Gaiman]
// 특별히 변환해야 할 내용이 없다면 flatten() 사용 ex) listOfLists.flatten()
}
시퀀스 : 중간 임시 컬렉션을 사용하지 않고도 컬렉션 연산을 연쇄 -> 효율적
asSequence 확장 함수 : 어떤 컬렉션이든 시퀀스로 바꿔줌(마지막에 다시 컬렉션으로 바꿔주는 과정 필요)
중간 연산 : 다른 시퀀스를 반환
최종 연산 : 결과를 반환
시퀀스의 모든 연산은 각 원소에 대해 순차적으로 적용
fun main(args: Array<String>) {
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)
println(listOf(1, 2, 3, 4).asSequence()
.map { it * it }.find { it> 3}) // 4
val people = listOf(Person("Alice", 29), Person("Bob", 31), Person("Charles", 31), Person("Dan", 21))
// map 다음에 filter 수정
println(people.asSequence().map(Person::name).filter { it.length < 4 }.toList()) // [Bob, Dan]
// filter 다음에 map 수정
println(people.asSequence().filter { it.name.length < 4}.map(Person::name).toList()) // [Bob, Dan]
}
// 상위 디렉터리의 시퀀스를 생성하고 사용
fun File.isInsideHiddenDirectory() = generateSequence(this) { it.parentFile }.any { it.isHidden }
fun main(args: Array<String>) {
// 자연수의 시퀀스를 생성하고 사용
val naturalNumbers = generateSequence(0) { it + 1 }
val numbersTo100 = naturalNumbers.takeWhile { it <= 100 }
println(numbersTo100.sum()) // 모든 지연 연산은 "sum"의 결과를 계산할 때 수행
// 상위 디렉터리의 시퀀스를 생성하고 사용
val file = File("/Users/svtk/.HiddenDir/a.txt")
println(file.isInsideHiddenDirectory()) // true
}
SAM 인터페이스(함수형 인터페이스) : 단일 추상 메소드를 가지는 인터페이스
컴파일러는 파라미터에 있는 람다를 자동으로 인스턴스로 변환 -> 무명 클래스에 있는 유일한 추상 메소드를 구현할 때 람다 본문을 메소드 본문으로 사용
/* 자바
void postponeComputation(int delay, Runnable computation);
*/
// object 선언을 사용하면서 람다와 동일한 코드 예시를 위함
val runnable = Runnable { println(42) } // 전역 변수로 컴파일 -> 프로그램 안에 단 하나의 인스턴스만 존재
fun handleComputation() {
postponeComputation(1000, runnable) // 모든 handleCompuation 호출에 같은 객체를 사용
}
// 주변 영역의 변수를 포획한 람다 -> 인스턴스를 매번 새로 만들어 사용
fun handleComputation2(id: String) { // 람다 안에서 "id" 변수 포획
postponeComputation(1000) { println(id) } // 이 함수를 호출할 때 마다 새로 Runnable 인스턴스 만듦
}
fun postponeComputation(delay: Int, computation: Runnable) {}
fun main(args: Array<String>) {
postponeComputation(1000) { println(42) } // 컴파일러가 자동으로 람다를 Runnable 인스턴스로 변환
handleComputation()
// 객체 식을 함수형 인터페이스 구현으로 넘기기
postponeComputation(1000, object: Runnable { // 메소드를 호출할 때 마다 새로운 객체가 생성됨
override fun run() {
println(42)
}
})
handleComputation2("minseo") // 메소드를 호출할 때 마다 새로운 Runnable 인스턴스 생성됨
}
SAM 생성자: 람다를 함수형 인터페이스의 인스턴스로 변환할 수 있게 컴파일러가 자동으로 생성한 함수, 사용자가 사용 가능
// SAM 생성자를 사용해 값 반환하기
fun createAllDoneRunnable() : Runnable {
return Runnable { println("All done!") } // SAM 생성자
}
fun main(args: Array<String>) {
// SAM 생성자를 사용해 값 반환하기
createAllDoneRunnable().run() // All done!
}
수신 객체 지정 람다 : 수신 객체를 명시하지 않고 람다의 본문 안에서 다른 객체의 메소드를 호출할 수 있게 하는 것
fun alphabet () : String {
val stringBuilder = StringBuilder()
return with(stringBuilder) { // 메소드를 호출하려는 수신 객체를 지정
for (letter in 'A'..'Z') {
this.append(letter) // "this"를 명시해서 앞에서 지정한 수신 객체의 메소드를 호출
}
append("\nNow I know the alphabet!") // "this"를 생략하고 메소드를 호출
this.toString() // 람다에서 값을 반환
}
}
fun alphabet2() = with(StringBuilder()) {
for (letter in 'A'..'Z') {
append(letter)
}
append("\nNow I know the alphabet!")
toString()
}
fun main(args: Array<String>) {
println(alphabet())
println(alphabet2())
}
apply : 항상 자신에게 전달된 객체(즉 수신객체)를 반환
fun alphabet3() = StringBuilder().apply {
for (letter in 'A'..'Z') {
append(letter)
}
append("\nMow I know the alphabet!")
}.toString()
fun main(args: Array<String>) {
println(alphabet3())
}
fun alphabet4() = buildString {
for(letter in 'A'..'Z') {
append(letter)
}
append("\nNow I know the alphabet!")
}
fun main(args: Array<String>) {
println(alphabet4())
}