가독성을 늘리기 위해 줄임말 문법을 계속 만들다보니 오히려 더 복잡해지는 감이 조금 있는 것 같다.
5.1 람다 식과 멤버 참조
val sum = { x: Int, y: Int -> x + y }
println(sum(1, 2)) // 3
그냥 만들어서 바로 실행할거면 인자로 받은 람다를 바로 실행해주는 run 라이브러리 함수를 실행하자.
{ println(42) }()
run { println(42) }
val people = listOf(Person("Alice", 29), Person("Bob", 31))
println(people.maxBy(Person::age))
println(people.maxBy({ p: Person -> p.age }))
println(people.maxBy() { p: Person -> p.age })
println(people.maxBy { p: Person -> p.age })
println(people.maxBy { p -> p.age })
println(people.maxBy { it -> it.age })
println(people.maxBy { it.age })

위 식들은 다 같은 역할을 하는 문법들이다. 너무 불필요하다 싶으면 IntelliJ에서 알려준다. 타입은 생략해도 추론할 수 있을 정도로 명확하면 생략해도 된다.
의문의 들만한건 람다 매개변수를 괄호 밖으로 왜 빼내도 되는건지 일 것이다. 마지막 매개변수가 람다식이라면 밖으로 빼내도 된다.
val people = listOf(Person("이몽룡", 29), Person("성춘향", 31))
val names = people.joinToString(separator = " ", transform = { p: Person -> p.name })
println(names)
val names2 = people.joinToString(" ") { p: Person -> p.name }
println(names2)
람다가 여러줄일 경우, 맨 마지막 줄을 return 한다.
람다를 함수 안에서 정의하면, 람다 밖의 함수 파라미터와 안의 변수들까지 사용 가능
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 getAge1 = { person: Person -> person.age }
val getAge2 = Person::age
val person = Person("Alice", 29)
println(getAge1(person))
println(getAge2(person))
위 두 개의 함수는 같은거임.
val action = { person: Person, message: String ->
sendEmail(person, message)
}
val nextAction = ::sendEmail
data class Person(val name: String, val age: Int)
val createPerson = ::Person
val p = createPerson("Alice", 29)
println(p)
fun Person.isAdult() = age >= 21
println(p.isAdult())
val predicate_lambda = { p: Person -> p.isAdult()}
println(predicate_lambda(p))
val predicate = Person::isAdult
println(predicate(p))
그냥 매개변수만 그대로 전달해주는 것 같다.
5.2 컬렉션 함수형 API
다른 언어에도 있는 filter, map, all, any, count, find는 생략
groupBy는 평소 알고 있는 것과 같은 역할
val people = listOf(Person("Alice", 29), Person("Bob", 31), Person("Carol", 31))
println(people.groupBy { it.age })

flatMap과 flatten은 중첩된 컬렉션 안의 원소를 처리한다.
val strings = listOf("adc", "def")
println(strings.flatMap { it.toList() }) // [a, d, c, d, e, f]

class Book(val title: String, val authors: List<String>)
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]
5.3 지연 계산(lazy) 컬렉션 연산
정의만 일단 해두고 계산은 나중에 하는 것이다. 컬렉션 뒤에 .asSequence()만 붙이면 된다. java의 stream과 같다고 함.
val people = listOf(
Person("Alice", 29),
Person("Bob", 31),
Person("Carol", 31),
Person("Dan", 21)
)
val peopleAnames = people.asSequence()
.map(Person::name)
.filter { it.startsWith("A") }
.toList()
println(peopleAnames) // ["Alice"]
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)
중요한 건 lazy 연산 시 정의만 되고, 꺼내는 함수가 실행될 때 원소 하나하나 순서대로 진행된다는 것. 즉, map을 적용한 리스트 저장 -> 필터 적용된 리스트 저장 -> 출력, 이 아니라 원래 리스트 원소 -> map적용 -> 필터 적용 으로 원소 하나를 끝까지 적용시키고 함.. 그래서 부가적인 메모리 저장공간이 필요 없어서 좋고, 미리 실행 안하니 자원낭비 안해서 좋고 한다는 것.
또 순서를 다르게 하면 같은 결과에서 더 좋아질 수도 있다.


asSequance 말고도 generateSequence도 있다고 한다. 이 함수는 이전의 원소를 인자로 받아 다음 원소를 계산한다.
val naturalNumbers = generateSequence(0) { it + 1 }
val numbersTo100 = naturalNumbers.takeWhile { it <= 100 }
println(numbersTo100.sum()) // 5050
// =======
fun File.isIfun File.isInsideHiddenDirectory() =
generateSequence(this) { it.parentFile }.any { it.isHidden }
val file = File("/Users/svtk/.HiddenDir/a.txt")
println(file.isInsideHiddenDirectory()) // true
5.4 자바 함수형 인터페이스 활용
자바의 메서드에 코틀린의 람다를 전달하기 위해선 Runnable 클래스에 넣어주면 된다. 컴파일러가 이 역할을 자동으로 해주고 있었다.
// 자바
void postponeComputation(int delay, Runnable computation);
postponeComputation(1000) { println(42) }
컴파일러가 자동으로 못바꿔주는 경우, SAM 생성자를 사용해야 한다. SAM 생성자는 람다를 함수형 인터페이스의 인스턴스로 벼환할 수 있게 컴파일러가 자동으로 생성한 함수다.
fun createAllDoneRunnable() : Runnable {
return Runnable { println("All done!") }
}
createAllDoneRunnable().run()
val listener = OnClickListener { view ->
val text = when (view.id) {
R.id.button1 -> "First button"
R.id.button2 -> "Second button"
else -> "Unknown button"
}
toast(text)
}
button1.setOnClickListener(listener)
button2.setOnClickListener(listener)
5.5. 수신 객체 지정 람다: with와 apply
파이썬이랑 비슷함. 여러 번 쓰고 닫을 거 그냥 한번에 열고닫기 보여주는 용도
fun alphabet(): String {
val result = StringBuilder()
for (letter in 'A'..'Z') {
result.append(letter)
}
result.append("\nNow I know the alphabet!")
return result.toString()
}
println(alphabet())
/*
ABCDEFGHIJKLMNOPQRSTUVWXYZ
Now I know the alphabet!
*/
fun alphabet(): String {
val result = StringBuilder()
return with(result) {
for (letter in 'A'..'Z') {
append(letter)
}
append("\nNow I know the alphabet!")
toString()
}
}

IntelliJ가 편하게 알려준다.
fun alphabet() = with(StringBuilder()) {
for (letter in 'A'..'Z') {
append(letter)
}
append("\nNow I know the alphabet!")
toString()
}
apply도 with와 거의 유사
fun alphabet() = StringBuilder().apply {
for (letter in 'A'..'Z') {
append(letter)
}
append("\nNow I know the alphabet!")
}.toString()
사실 stringbuilder로 string으로 변환하여 반환해주는 표준 라이브러리의 buildString이 있었다.
fun alphabet() = buildString {
for (letter in 'A'..'Z') {
append(letter)
}
append("\nNow I know the alphabet!")
}
'CS > 코틀린 인 액션' 카테고리의 다른 글
7. 연산자 오버로딩과 기타 관례 (0) | 2023.05.03 |
---|---|
6. 코틀린 타입 시스템 (0) | 2023.04.30 |
4. 클래스, 객체, 인터페이스 (0) | 2023.04.28 |
3. 함수 정의와 호출 (0) | 2023.04.24 |
2. 코틀린 기초 (0) | 2023.04.23 |