[Kotlin] 익명 함수와 함수 타입
익명 함수란? (Anonymosu function)
- 이름이 없는 함수이다
- 주로 다른 함수의 인자로 전달되거나 반환되는 형태로 사용된다.
- 익명 함수는 아래 예제와 같이 중괄호({})를 열고 닫아 그 내부에 작성합니다.
- 또 다른 명칭으로 람다(lambda)이라 한다.
fun main(args: Array<String>) {
//count라는 함수의 인자로 익명 함수를 선언 했다.
val numLetters = "Mississippi".count({letter->
letter == 's'
})
println(numLetters)
}
fun main(args: Array<String>) {
//위 코드를 람다식으로 바꾼 형태
val numLetters = "Mississippi".count(){letter->
letter == 's'
}
println(numLetters)
}
함수 타입
- 익명 함수도 타입을 가지며, 이것을 함수 타입이라고 한다.
- 함수 타입의 변수들은 값으로 익명 함수를 저장한다.
- 함수 타입은 해당 함수의 입력, 출력, 매개변수에 따라 다양하게 선언된다.
- 아래 코드에서 함수 타입은 "()->String" 입니다
- 아래 코드의 함수 타입을 해석하면 "매개변수는 없으며 반환 값은 String이다"입니다.
fun main(args: Array<String>) {
val helloFunction: ()->String = {
val currentYear = 2019
"Hello current Year : $currentYear"
}
println(helloFunction())
}
암시적 반환
- 익명 함수에는 return 키워드가 없으며 암시적으로 마지막 코드 결과를 반환합니다.
함수 인자
- 익명 함수도, 일반 함수와 동일하게 어떤 타입의 인자도 받을 수 있고 0개 이상의 매개변수를 받을수 있다.
- 아래 코드는 String 형태의 인자를 받아, 익명 함수에서 사용하는 예제이다
fun main(args: Array<String>) {
val helloFunction: (String) -> String = { currentYear ->
"Hello current Year : $currentYear"
}
println(helloFunction("2020"))
}
익명 함수는 Kotlin 표준 라이브러리에 있는 대부분 함수를 구현하는 데 사용되고 있으며 익숙해지는 것이 좋다
it 키워드
- 인자가 1개인 익명 함수는 매개변수 이름을 지정하지 않고 it 키워드를 사용할 수 있다.
- 인자가 2개 이상이면 it 키워드를 사용할 수 없다.
- 위에 코드를 it 키워드를 사용한 형태로 전환해 보겠다.
fun main(args: Array<String>) {
val helloFunction: (String) -> String = {
"Hello current Year : $it"
}
println(helloFunction("2020"))
}
다수의 인자 받기
- 2개 이상의 인자를 받아 it 키워드는 사용할 수 없다.
fun main(args: Array<String>) {
val helloFunction: (String, Int) -> String = { year, age ->
"Hello current Year : $year \nyour age is $age"
}
println(helloFunction("2020",28))
}
타입 추론 지원
- 변수의 타입을 작성하지 않게 해주는 타입 추론을 함수 타입에서도 지원합니다.
- 암시적 반환과 타입 추론이 같이 사용되면 가독성이 떨어질 수 있다.
fun main(args: Array<String>) {
val helloFunction = { year:String, age:Int ->
"Hello current Year : $year \nyour age is $age"
}
println(helloFunction("2020",28))
}
함수를 인자로 받는 함수 정의하기
- 아래 코드의 printHelloFunction() 함수는 함수를 인자로 받아 실행하고 있습니다.
fun main(args: Array<String>) {
val helloFunction = { year:String, age:Int ->
"Hello current Year : $year \nyour age is $age"
}
printHelloFunction(helloFunction)
}
fun printHelloFunction(helloFunction:(String, Int)->(String)){
val msg:String = helloFunction("2020",28)
println(msg)
}
단축 문법
- 함수에서 마지막 매개변수로 함수 타입을 받을 때는 익명 함수 인자를 둘러싼 괄호를 생략할 수 있다.
[인자가 1개 - 단축 문법 적용 전]
printHelloFunction({ year:String, age:Int ->
"Hello current Year : $year \nyour age is $age"
})
[인자가 1개 - 단축 문법 적용 후]
printHelloFunction { year: String, age: Int ->
"Hello current Year : $year \nyour age is $age"
}
[인자가 2개 - 단축 문법 적용 전]
printHelloFunction (2000,{ year: String, age: Int ->
"Hello current Year : $year \nyour age is $age"
})
[인자가 2개 - 단축 문법 적용 후]
printHelloFunction (2000){ year: String, age: Int ->
"Hello current Year : $year \nyour age is $age"
}
인라인(inline) 함수로 만들기
- 람다를 사용하면 유연성이 좋은 프로그램을 작성 가능하나, JVM에서는 객체로 생성 및 람다가 사용되는 곳에서 마다
메모리 할당을 수행하므로 메모리 소비가 심하다.
- 위와 같은 상황에 람다 사용 부담을 없애는 인라인(inline)이라는 최적화 방법을 제공한다
- 인라인을 사용하면 람다의 객체 사용과 변수의 메모리 할당을 JVM이 하지 않아도 된다.
- 인라인을 사용하려면 아래 코드와 같이 inline 키워드를 앞에 추가만 해주면 된다.
inline fun printHelloFunction(price:Int ,function: (String, Int) -> (String)) {
val msg: String = function("2020", 28)
println(msg)
}
- 인라인 키워드를 추가하면 printHelloFunction이 호출될 때 람다가 객체로 전달되지 않는다.
(코틀린 컴파일러가 바이트코드를 생성 시 람다 코드가 포함된 함수를 전체 복사 후
이 함수를 호출하는 코드에 붙여 넣기 하여 교체하기 때문이다.)
- 인라인 키워드가 사용된 재귀 함수는 코틀린 컴파일러에 의해 루프(예를 들어, for) 형태로 변경된다.
함수 참조 (Function reference)
- 함수를 인자로 전달하는 방식이 아닌 함수 참조를 인자로 전달하는 방식이 있다.
- 기존 함수를 람다 대신 사용할 수 있다.
- 아래 코드는 printCost()라는 기존 함수를 calCost()라는 함수의 매개변수로 참조하여 전달한 예제이다.
fun main(args: Array<String>) {
calCost(1,2,::printCost)
}
//함수 참조 사용
fun calCost(cost1:Int, cost2:Int, printCost:(Int)->Unit){
printCost(cost1+cost2)
}
//참조할 함수
fun printCost(cost:Int){
println(cost)
}
반환 타입으로 함수 타입 사용하기
- 함수 타입도 반환 타입에 사용될 수 있다.
- 반환 타입을 쓰는 이유는, 익명 함수를 만드는 factory과정이라고 볼 수 있다.
- 아래 코드는 createUserStatusFunc() 함수에서 유저의 이름과 나이는 미리 생성하며
몸무게 생성과정만 익명 함수로 만들어 반환하였다.
반환받은 익명 함수를 실행시켜 언제든지 몸무게를 입력받아 문자열 생성이 가능하다.
fun main(args: Array<String>) {
val userStatusNameAndAge: (String) -> String = createUserStatusFunc("Choi", 28)
printStatus(userStatusNameAndAge)
}
//유저 이름, 나이를 먼저 입력 받고
//몸무게는 익명 함수가 실행 될 때 입력 받는다.
fun createUserStatusFunc(): (String) -> String {
val userName = "Choi"
val userAge = 28
return { userWeight: String ->
"유저의 이름 : $userName \n유저의 나이 : $userAge \n유저의 몸무게 : $userWeight"
}
}
//람다 함수 실행
fun printStatus(userStatusNameAndAge:(String)->String){
println(userStatusNameAndAge("80"))
}
- createUserStatusFunc()에서 사용된 userName, userAge라는 변수는 어떻게 printStatus()에서 정상적으로 사용되는 걸까? -> Kotlin의 람다는 클로저(closure) 이기 때문이다.
- 다른 함수를 인자로 받거나 반환하는 함수를 고차 함수(higher-order function)라고 한다.
Kotlin 람다는 클로저
- 매개변수로 받은 함수의 (매개변수와 변수)를 사용할 수 있는 것을 의미한다.
람다(Kotlin) vs 익명 클래스(Java)
- Java 8과 같이 함수 타입을 제공하지 않는 언어와 비교해서 함수 타입을 사용하면 진부한 코드가 줄어들고
유연성이 증가한다.
- Java는 객체지향과 람다 표현식 모두를 지원하지만, 함수의 매개변수나 변수에 함수를 정의할 수 있는 기능이 없다.
- Java와 Kotlin 양쪽 다 익명 함수(Java에선 익명 구현 객체)를 지원하여 함수(메서드)에 전달이 가능하지만
Java 측엔 조금 더 코드가 추가된다.
아래 코드에 Java와 Kotlin으로 비교를 해보았다.
1. Java는 익명 구현 객체를 사용하기 위해 interface를 생성이 필수
Kotlin은 익명 함수를 바로 정의 가능
2. Java는 익명 객체를 사용 시 간편식을 지원하지 않지만
Kotlin은 익명 함수 사용시 (함수 내부 정의) 혹은 it키워드, 암시적 반환, 클로저 같은 간편식 들을 지원한다.
//Java - 익명 구현 객체를 생성 및 메서드에 인자로 사용
public static void main(String[] args) {
UserStatus status = (userName, age) -> {
return userName + " " + age;
};
System.out.println(status.printStatus("Choi", 28));
}
interface UserStatus {
String printStatus(String userName, int age);
}
//Kotlin - 익명 함수 생성 및 함수 인자로 사용
println({ userName: String, userAge: Int ->
"$userName $userAge"
}("Choi",28))