람다를 인자로 받는 함수를 정의하려면? 타입을 어떻게 선언할지 알아 보자.
// 컴파일러에서 두 개를 함수 타입으로 추론을 함.
// 각각 Int unit 반환하는걸로 인식.
val sum = (x: Int, y: Int) => x + y
val action = { println(42) }
함수타입을 정의하려면 -> 뒤에 반환 타입을 지정하면 됨.
unit 타입 경우 생략해도 되지만 함수 타입! 을 선언할땐 반환타입 반드시 명시해야됨! Unit빼먹지말기.
// 널이 될수 있는 함수타입 변수도 가능.
var canReturnNull: ((Int, Int) -> Int)? = { x, y -> null }
고차함수를 구현해보자
// Declare a function type parameter for operation that takes two Int arguments and returns an Int
fun twoAndThree(operation: (Int, Int) -> Int) {
// Call the operation function with arguments 2 and 3 and store the result in the result variable
val result = operation(2, 3)
// Print the result string
println("The result is $result")
}
// Call the twoAndThree function with a lambda expression that adds two numbers
twoAndThree { a, b -> a + b } // The result is 5
// Call the twoAndThree function with a lambda expression that multiplies two numbers
twoAndThree { a, b -> a * b } // The result is 6
단순한 문법소개 함수 이름 뒤에 괄호를 넣고 원하는 인자넣은뒤 콤마로 분리!
Filter함수를 구현해 보자.
// filter 함수를단순하게만든버전구현하기
fun String.filter(predicate: (Char) -> Boolean): String {
val sb = StringBuilder()
for (index in 0 until length) {
val element = get(index)
if (predicate(element)) sb.append(element)
}
return sb.toString()
}
println("ablc".filter { it in 'a'..'z' }) // Output: "abc"
길다 제목 참..
파라미터로 넘기는 함수를 디폴트값을 지정할 수 있다.
fun <T> Collection<T>.joinToString(
separator: String = ", ",
prefix: String = "",
postfix: String = ""
): String {
val result = StringBuilder(prefix)
for ((index, element) in this.withIndex()) {
if (index > 0) result.append(separator)
result.append(element)
}
result.append(postfix)
return result.toString()
}
예시 코드 result.append(element) 는 암시적이게 대해 toString 메서드 호출
허나 toString 메서드로만 고정되 문자열로 반환. 따라 서 다른 방법도필요
허나 파라미터를인자로 넘겨주면 불편할 수도 있으니 고정값을 사용하는법을 하자
fun <T> Collection<T>.joinToString(
separator: String = ", ",
prefix: String = "",
postfix: String = "",
transform: (T) -> String = { it.toString() }
): String {
val result = StringBuilder(prefix)
for ((index, element) in this.withIndex()) {
if (index > 0) result.append(separator)
result.append(transform(element))
}
result.append(postfix)
return result.toString()
}
val letters = listOf("Alpha", "Beta")
// Example usage of joinToString function
println(letters.joinToString()) // default conversion
println(letters.joinToString(transform = { it.toLowerCase() })) // lowercase transformation
println(letters.joinToString(separator = "! ", postfix = "! ", transform = { it.toUpperCase() })) // uppercase transformation with custom separator and postfix
// Invoke 예제.
// Define a function-like object using a lambda expression
val myFunction: (String) -> Unit = { str -> println(str) }
// Call the function using the invoke method
myFunction.invoke("Hello, world!") // Output: "Hello, world!"
// Alternatively, call the function using the shorthand syntax
myFunction("Hello again!") // Output: "Hello again!"
fun <T> Collection<T>.joinToString(
separator: String = ", ",
prefix: String = "",
postfix: String = "",
transform: ((T) -> String)? = null
): String {
val result = StringBuilder(prefix)
for ((index, element) in this.withIndex()) {
if (index > 0) result.append(separator)
val str = transform?.invoke(element) ?: element.toString()
result.append(str)
}
result.append(postfix)
return result.toString()
}
enum class Delivery {
STANDARD, EXPEDITED
}
class Order(val itemCount: Int)
fun getShippingCostCalculator(delivery: Delivery): (Order) -> Double {
if (delivery == Delivery.EXPEDITED) {
return { order -> 6 + 2.1 * order.itemCount }
}
return { order -> 1.2 * order.itemCount }
}
val calculator = getShippingCostCalculator(Delivery.EXPEDITED)
println("Shipping costs: ${calculator(Order(3))}")
Shipping c o s t s 12.3
data class SiteVisit(
val path: String,
val duration: Double,
val os: OS
)
enum class OS {
WINDOWS, LINUX, MAC, IOS, ANDROID
}
val log = listOf(
SiteVisit("/", 34.0, OS.WINDOWS),
SiteVisit("/", 22.0, OS.MAC),
SiteVisit("/login", 12.0, OS.WINDOWS),
SiteVisit("/signup", 8.0, OS.IOS),
SiteVisit("/", 16.3, OS.ANDROID)
)
// 확장함수로 간결하게
fun List<SiteVisit>.averageDurationFor(os: OS) =
filter { it.os == os }
.map(SiteVisit::duration)
.average()
val averageWindowsDuration = log.filter { it.os == OS.WINDOWS }
.map(SiteVisit::duration)
.average()
println(averageWindowsDuration) // Output: 23.0
println(log.averageDurationFor(OS.MAC)) // Output: 22.0
// 고차함수를 이용해 간결하게
fun List<SiteVisit>.averageDurationFor(predicate: (SiteVisit) -> Boolean) =
filter(predicate)
.map(SiteVisit::duration)
.average()
println(log.averageDurationFor {
it.os in setOf(OS.ANDROID, OS.IOS)
}) // Output: 12.15
println(log.averageDurationFor {
it.os == OS.IOS && it.path == "/signup"
}) // Output: 8.0
inline 함수에 사용시 함수의 본문이 인라인이 됨.
함수 본문을 번역해서 바이트코드로 컴파일 함.
그러나 람다를 변수에 저장하거나 나중에 사용하는 경우 인라이닝 불가능
val anotherLambda: () -> Unit
if (someCondition) {
anotherLambda = myLambda
} else {
anotherLambda = { println("Performing another action") }
}
inlineFunction(anotherLambda) // In this case, inlining is not possible
```
그러나 인라이닝은 컴파일된 코드의 크기를 증가해 항상 유익한건 아님.
과도한 사용은 코드 부풀림 발생 -> 캐시미스 유발 컴파일 코드 효율성 떨어 트림..
inline fun add(a: Int, b: Int): Int {
return a + b
}
inline fun processList(list: List<Int>, action: (Int) -> Int): List<Int> {
return list.map(action)
}
inline fun square(x: Int): Int {
return x * x
}
fun main() {
// Small function example
val sum = add(3, 4)
println("Sum: $sum")
// Higher-order function example
val doubled = processList(listOf(1, 2, 3)) { it * 2 }
println("Doubled: $doubled")
// Loop optimization example
println("Squared:")
for (i in 1..5) {
println(square(i))
}
}
Sum: 7
Doubled: [2, 4, 6]
Squared:
1
4
9
16
25
```
컬렉션에 작용하는 코틀린 표준 라이브러리 성능 알아보기
//람다 구현
data class Person(val name: String, val age: Int)
val people = listOf(Person("Alice", 29), Person("Bob", 31))
println(people.filter { it.age < 30 }) // [Person(name=Alice, age=29)]
//람다 구현 X
data class Person(val name: String, val age: Int)
val people = listOf(Person("Alice", 29), Person("Bob", 31))
val result = mutableListOf<Person>()
for (person in people) {
if (person.age < 30) result.add(person)
}
println(result) // [Person(name=Alice, age=29)]
코틀린의 Filter함수는 인라인 함수 임.
filter 함수가 호출될 때, 람다 표현식의 바이트 코드와 함께 호출 위치에 직접 복사된다는 것을 의미합니다. 이로 인해 성능 면에서 우수한 최적화된 코드가 생성 됨
람다 함수를 컬렉션과 함께 인라인으로 사용되는 경우가 많으며
이런식으로 성능이 향상이 됨.
그래서 일반함수를 사용할때 필터 대신 사용하면 해당 함수를 별도로 호출해서
오버헤드가 추가되고 성능상 안좋을 수 있음
data class Person(val name: String, val age: Int)
fun main() {
val people = listOf(Person("Alice", 29), Person("Bob", 31))
// using lambda function
val lambdaResult = people.filter { it.age < 30 }
println(lambdaResult)
// using regular function
val regularResult = getYoungerThan30(people)
println(regularResult)
}
fun getYoungerThan30(people: List<Person>): List<Person> {
val result = mutableListOf<Person>()
for (person in people) {
if (person.age < 30) {
result.add(person)
}
}
return result
}
여기 예제에서도 결과는 같지만 일반 함수는 함소 호출이 추가 되므로 오버헤드발생.
책에서는 시퀀스(stream) 사용시 인라인 하지 않기 때문에 작은 크기 컬렉션은 일반 컬렉션 연산이 더나음
대략적으로 10-100개 미만의 요소가 있는 컬렉션의 경우 성능 차이가 크지 않을 수 있음 실무에선 .. 그냥 람다써도 될 듯?
// 함수 호출 비용 절감
fun add(a: Int, b: Int): Int {
return a + b
}
// Lambda function
val addLambda: (Int, Int) -> Int = { a, b -> a + b }
// Usage
val result1 = add(2, 3) // Function call
val result2 = addLambda(2, 3) // Lambda call
// 개체 생성 방지:
// 이 예에서 performAction 함수는 람다를 인수로 사용합니다.
// 람다 호출을 사용하여 호출하면 람다에 해당하는 객체가 생성되지 않습니다.
// 반대로 개체 식을 사용하여 호출하면 람다 인터페이스를 구현하는 익명 클래스가 생성됩니다.
fun performAction(action: () -> Unit) {
action()
}
// Usage
performAction { println("Hello World") } // Lambda call
// Equivalent code using object expression
performAction(object : () -> Unit {
override fun invoke() {
println("Hello World")
}
})
// 비로컬 제어 흐름
// 비로컬 제어 흐름은 중간 코드를 건너뛰고
// 프로그램의 한 지점에서 다른 지점으로 제어를 전송하는 기능
inline fun performCalculation(numbers: List<Int>, operation: (Int) -> Int): Int {
numbers.forEach {
val result = operation(it)
if (result < 0) return result // Non-local return
}
return 0
}
// Usage
val numbers = listOf(1, 2, 3, -4, 5)
val result = performCalculation(numbers) {
if (it < 0) return@performCalculation -1 // Labelled return
it * 2
}
println(result) // -1
자원을 획득하고 작업을 마친후 자원을 해제하는 자원관리?
자바에서는 try-with-resource를써 해당객체를 획득하고 자동으로 닫아준다.
코틀린에선 use라는 예약어가있음
fun readFirstLineFromFile(path: String): String {
BufferedReader(FileReader(path)).use { br ->
return br.readLine()
}
}
새로운사실! use는 함수를 닫을 수 있는(Closeable)자원에 대한 확장 함수다! (람다인자로받음)
use는 또 인라인 함수임.
람다 본문안에 사용한 Return은 넌로컬 return 이문은 람다가 아니라 readFristLineFromFile 함수를 끝내면서 값 반환.
외부 함수나 클로저에서 반환하는 반환문을 의미
비로컬 반환문은 여러 수준의 중첩된 함수나 클로저에서 빠져나올 수 있도록 해줌
책에서는 자신을 둘러싸고 있는 블록보다 더 바깥에 있는 다른블록을 반환하게 만드는 것을 말한다.
책에 나온내용이 더이해하기 쉽네..
바깥쪽에 함수를 반환시킬수 있는.. 음음.. 람다를 인자로 받는함수가 인라인함수인경우뿐
람다식의 로컬 Return 사용 가능 , 이는 break랑 비슷함
fun lookForAlice(people: List<Person>) {
people.forEach label@{
if (it.name == "Alice") return@label
}
println("Alice might be somewhere")
}
label을 붙여서 표시함.