코틀린에서 함수는 변수나 데이터 구조에 저장될 수 있고 고차함수의 인자와 반환값에 사용될 수 있습니다.
코틀린은 함수를 다루는 선언을 위해서 function 타입을 사용합니다.
함수의 종류에 따라 함수형을 선언하는 방법이 조금씩 다릅니다.
모든 function타입의 선언은 괄호안에 파라미터의 타입을 나열하고 -> 화살표 뒤에 반환타입을 적습니다.
ex. (Int, Int) -> Int
파라미터가 존재하지 않는 경우에 괄호안에 파라미터를 생략하고 반환형만 표현하면 됩니다.
ex. () -> Int
function 타입은 receiver 타입을 가질수도 있습니다. receiver 타입은 함수형 선언 앞에 .(dot)과 함께 사용됩니다.
아래의 예는 Class 객체에서 호출되는 파라미터가 Int형 2개이고 반환형이 Int인 함수를 의미합니다.
ex. Class.(Int, Int) -> Int
suspend 함수는 function 타입 앞에 suspend 키워드를 사용하여 표현됩니다.
ex. suspend (Int, Int) -> Int
function타입의 파라미터에 대해 이름을 붙일 수도 있습니다. 파라미터의 이름은 파라미터의 의미를 알리는데 사용될 수 있습니다.
ex. (numb: Int) -> String
function 타입의 nullable을 표현하기 위해서는 괄호를 다음과 같이 사용하면 됩니다.
ex. ((Int, Int) -> Int)?
function 타입의 인스턴스를 얻는 몇가지 방법이 있습니다.
람다표현식을 사용하는 법 (ex. val func = { a: Int, b:Int -> a + b})
익명 함수를 사용하는 법 (ex. val func = fun(a: Int, b: Int): Int { return a + b })
존재하는 함수 선언에 대해 호출 가능한 참조를 사용하는 법
함수타입을 인터페이스로 구현하는 사용자 정의 클래스의 인스턴스를 사용하는 법
class FuncInterface: (Int, Int) -> Int {
override fun invoke(p1: Int, p2: Int): Int {
return (p1 + p2)
}
}
// 함수타입을 인터페이스로 구현하는 사용자 정의 클래스
fun List<Int>.print() {
println("this is Int List")
}
fun main() {
val print2: List<Int>.() -> Unit = List<Int>::print // 확장함수의 인스턴스
val intArrayConstructor: (Int) -> IntArray = ::IntArray // IntArray 생성자의 인스턴스
val intArray = intArrayConstructor(5) // 생성자 인스턴스를 통해 intArray 인스턴스 생성
val mySize: IntArray.() -> Int = IntArray::size // IntArray의 size 프로퍼티 인스턴스
val lst = listOf<Int>()
val funcClass = FuncInterface()
lst.print2() // 확장함수 인스턴스를 통한 함수 사용
println(intArray.mySize()) // size 프로퍼티 인스턴스를 사용하여 사이즈 확인
val str = "12345"
val strToInt: String.() -> Int = String::toInt // 멤버함수의 인스턴스
println(str.strToInt() + 1) // 멤버함수 인스턴스를 통해 함수 사용
println(funcClass(1, 2))
}
함수 타입의 값은 invoke()를 사용하여 호출할 수 있습니다.
val func: (Int, Int) -> Int = ::add
println(func(1, 2))
println(func.invoke(1, 2))
만약, 그 값이 receiver 타입을 갖는다면 receiver 객체는 반드시 첫번째 파라미터로 들어와야합니다.
receiver 타입을 갖는 함수타입을 호출하는 또 다른 방법은 receiver 객체를 함수 앞에 붙이는 방법입니다.
fun Int.add(a: Int): Int {
return (this + a)
}
fun main() {
val func: Int.(Int) -> Int = Int::add
println(func(1, 2))
println(func.invoke(1, 2))
println(1.func(2))
}
람다 식과 익명 함수는 함수 리터럴입니다. 함수 리터럴은 선언되지 않았지만 즉시 표현식으로 전달되는 함수입니다.
fun numbComp(a: Int, b: Int, comp: (Int, Int) -> Boolean) = comp(a, b)
fun main() {
val res = numbComp(1, 2 ) { x: Int, y: Int ->
x < y
}
println(res) // true
val res2 = numbComp(1, 2, fun(x: Int, y: Int) = x < y)
println(res2) // true
}
람다 표현식의 완전한 문법적인 형태는 다음과 같습니다.
ex. val sum: (Int, Int) -> Int = { x: Int, y: Int -> x + y }
람다 표현식은 항상 중괄호로 감싸져 있어야 합니다.
파라미터의 선언은 중괄호 안에서 이루어지며, 파라미터의 타입이 선언에 명시되어 있으면 중괄호 내에서는 생략이 가능합니다.
ex. val sum: (Int, Int) -> Int = { x, y -> x + y }
함수의 body는 -> 이후부터 시작합니다.
람다의 추론된 반환 타입이 Unit이 아니라면, 람다 함수 body의 마지막은 반환값이 됩니다.
람다 표현식의 생략된 선언은 다음과 같습니다.
ex. val sum = {x: Int, y: Int -> x + y }
코틀린 코딩 컨벤션에 따르면, 함수의 마지막 파라미터가 함수라면 그 함수에 해당하는 인자로 넘어오는 람다표현식은 괄호 밖에 쓸 수 있습니다.
fun numbComp(a: Int, b: Int, comp: (Int, Int) -> Boolean) = comp(a, b)
fun main() {
val res = numbComp(1, 2) { x: Int, y: Int ->
x < y
} // this case is passing trailing lambdas
println(res) // true
}
만약, 람다표현식이 함수의 유일한 인자인 경우 괄호를 생략할 수 있습니다.
run { pritln("hello") }
컴파일러가 파라미터 없이 람다표현식을 파싱할 수 있으면, 파라미터는 선언되지 않아도 됩니다. 또한 ->도 생략될 수 있습니다. 파라미터는 암시적으로 it으로 선언됩니다.
fun main() {
val intArray = intArrayOf(1, 2, 3)
val filteredArr = intArray.filter { it < 3 }
println(filteredArr.toList())
}
람다표현식에서 한정된 return 문법을 사용하여 명시적으로 값을 반환할 수 있습니다. 반면에 마지막 표현식의 값은 암시적으로 반환됩니다.
ints.filter {
val shouldFilter = it > 0
shouldFilter
}
ints.filter {
val shouldFilter = it > 0
return@filter shouldFilter
}
람다 표현식의 파라미터가 사용되지 않으면, 그 파라미터의 이름을 _(underscore)로 둘 수 있습니다.
map.forEach { (_, value) -> println("$value!") }
람다표현식은 함수의 반환타입을 지정하는 기능이 없습니다. 대부분의 상황에서 반환 타입이 자동으로 추론되기 때문에 그 기능은 불필요 합니다. 하지만 명시적으로 반환 타입을 특정하고 싶다면, 익명 함수라는 대안을 사용할 수 있습니다.
fun(x: Int, y: Int): Int = x + y
An anonymous function looks very much like a regular function declaration, except its name is omitted. Its body can be either an expression (as shown above) or a block:
익명함수는 이름이 없다는 것을 제외하고는 일반적인 함수의 선언과 매우 비슷합니다. 익명함수의 바디는 block을 사용할수도 있고 =을 사용할 수도 있습니다.
fun(x: Int, y: Int): Int {
return x + y
}
파라미터와 반환타입은 일반적인 함수와 똑같이 명시됩니다. 문맥에 따라 파라미터 타입이 추론 가능하면 파라미터의 타입을 생략할 수 있습니다.
fun numbComp(a: Int, b: Int, comp: (Int, Int) -> Boolean) = comp(a, b)
fun main() {
val res = numbComp(1, 2, fun(x, y) = x < y)
// parameter's type is omitted
println(res) // true
}
익명 함수에 대한 반환 타입 추론은 일반 함수와 똑같이 작동하빈다. 반환 타입은 표현식이 body인 함수에 대해서는 자동으로 추론될 수 있습니다. 하지만 block body인 함수는 명시적으로 표시해주어야 합니다.
파라미터로 익명함수를 넘길때는 괄호 밖에 적을 수 없습니다. 괄호 밖에 쓰는 문법을 사용하고 싶다면 람다식을 사용하세요!
람다표현식과 익명함수의 다른 차이점은 비지역 반환입니다. 라벨이 없는 return문은 항상 fun 키워드와 함께 선언된 함수로부터 반환합니다.
람다표현식 내부의 return은 람다식이 있는 외부함수에서 반환이 발생하고, 익명함수는 익명함수의 그 자체에서 반환이 발생합니다.
inline fun Temp(a: Int, func: () -> Unit) {
println(a)
func() // return문으로 프로그램 종료...
println("end")
}
fun main() {
Temp(5) {
println("here is lambda")
return//@Temp를 붙이면 정상작동
}
}
람다 표현식 또는 익명함수는 클로져에 접근할 수 있습니다. 그리고 클로져는 바깥 스코프에 선언된 변수를 포함합니다. 클로져에서 포획된 변수들은 람다식 내부에서 수정할 수 있습니다.
fun main() {
var num = 0
val lambda = {
num += 1
}
lambda()
lambda()
println(num)
}
코틀린은 receiver가 있는 함수타입의 인스턴스를 recevier 객체를 사용하여 호출할 수 있게 합니다.
호출에서 전달된 receiver 객체는 함수의 바디 내부에서 암시적으로 this가 됩니다. 즉, receiver 객체의 멤버에 대해 this를 사용하여 접근할 수 있습니다.
fun main() {
val sum: String.(Int) -> Int = { numb -> this.length + numb }
println(sum("abcde", 2))
println(sum.invoke("abcde", 2))
println("abcde".sum(2))
}