상세 컨텐츠

본문 제목

[Kotlin] 익명 함수와 함수 타입

Programming language/Kotlin

by choiDev 2020. 12. 5. 00:54

본문

익명 함수란? (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))

 

'Programming language > Kotlin' 카테고리의 다른 글

[Kotlin] 표준함수  (0) 2020.12.06
[Kotlin] 문자열  (0) 2020.12.05
[Kotlin] 함수(Function)  (0) 2020.12.04
[Kotlin] 조건문과 조건식 (If, When, In)  (0) 2020.09.24
[Kotlin] Null 안전과 예외  (0) 2020.09.24

관련글 더보기