본문 바로가기

코틀린

코틀린: 확장 함수와 수신 객체를 갖는 함수 타입(1/3)

책 여기 저기 흩어져 있던 확장 함수에 관련된 내용을 한 군데 모았습니다.  자세한 내용은 쉽게 다가가는 최신 프로그래밍: 코틀린 - 3.2 함수형 프로그래밍, 3.4 람다 식: 고급 3.6.2 확장 함수를 참고하기 바랍니다. 확장 함수와 관련된 내용이 이렇게 많았다구요? 놀랍죠! 4.16 범위 함수(scope function) 에서 소개한 5개 범위 함수 중 with를 제외한 4개(also, apply, let, run)가 모두 확장 함수입니다. 

확장(extension)이란 기능을 추가한다는 뜻입니다. 정확히 말하면 기존 클래스(class)에 기능을 추가한다는 뜻입니다. 클래스에서 제공하는 함수 대신 응용 목적에 필요한 기능을 추가하는 겁니다. 클래스에 추가하는 기능은 대부분 함수이지만 속성도 추가할 수 있습니다. 를 사고 나서 내 스타일에 맞게 튜닝하는 것과 같습니다. 확장 함수와 2.2 연산자 오버로딩(연산자 중복)을 헷갈리면 안됩니다. 연산자 오버로딩에서는 연산 기호(*)와 이 기호에 대응되는 함수 이름(times)이 1:1 관계로 미리 정해져 있습니다.

■ 확장 함수(extension function)
확장 함수 안에서는 클래스의 멤버 함수처럼 수신 객체(receiver)를 this로 참조할 수 있습니다. 아래 예제는 Int 클래스에 확장 함수 multiple()을 추가했습니다. 확장 함수가 일반 함수와 다른 점은 "Int.multiple( )" 과 같이 함수 이름(multiple) 앞에 수신 객체 타입(Int)을 붙이는 점입니다. 마침표(dot, .)는 multiple()이 클래스 Int의 멤버 함수임을 나타냅니다. 함수 multiple() 은 수신 객체에 형식 인자를 곱한 결과를 반환합니다.  "1.multiple(3)"에서 "1"이 수신 객체로 전달되는 객체이며, this가 참조하는 객체입니다.

fun Int.multiple(a: Int): Int {
    return this * a
}

fun main() {
    println(1.multiple(0))  // 1*0 = 0
    println(1.multiple(3))  // 1*3 = 3
    println(5.multiple(10)) // 5*10 = 50
}

 

■ 수신 객체를 갖는 함수 타입: 확장 함수 타입
확장 함수를 정의할 필요없이 기존 클래스의 메소드를 참조할 수도 있습니다.

아래 예제에서 intMultiple을 fun이 아닌 val로 선언해서 당황했나요? 변수 intMultiple을 함수 타입으로 선언했기 때문에,  intMultiple은 당당히 함수를 참조할 수 있습니다. 이렇게 지정한 것을 수신 객체를 갖는 함수 타입이라고 부르며, 일반 함수 타입과 구분하기 위해 확장 함수 타입(extension function type)이라고 부릅니다.  함수 타입 "Int . (Int)"에서 왼쪽 Int가 수신 객체입니다. 일반 함수를 참조할 때는 ::함수_이름 이지만, Int 클래스의 멤버 함수 times를 참조할 때는 Int::times를 사용합니다. 멤버 함수 이름 뒤에 괄호를 붙이면 안됩니다. 이제 9.intMultiple(7) 또는 일반 함수처럼 intMultiple(9, 7)로도 사용할 수 있습니다.

fun main() {
    val intMultiple: Int.(Int) -> Int = Int::times
    
    println(9.intMultiple(7))
    println(intMultiple(9, 7))
}

클래스의 멤버 함수를 참조하는 대신 람다 식을 사용해 구현할 수도 있습니다. 변수 intMultiple은 이제 람다 식을 참조하지만 함수 타입은 그대로입니다.

fun main() {
    val intMultiple: Int.(Int) -> Int = { this.times(it) }
    // val intMultiple: Int.(Int) -> Int = { times(it) }  // this는 생략할 수 있습니다.
    
    println(9.intMultiple(7))
    println(intMultiple(9, 7))
}

람다 식 대신 익명 함수를 사용해도 됩니다.

fun main() {
    val intMultiple = fun Int.(a:Int) = this * a  // 함수 타입 생략
    // val intMultiple: Int.(Int) -> Int = fun Int.(a:Int) = this * a  

    println(9.intMultiple(7))
    println(intMultiple(9, 7))
}