확장 함수(extension function)를 이용해서 기존 자바 API를 재작성하지 않고도 코틀린이 제공하는 여러 편리한 기능을 사용할 수 있다.

확장 함수는 어떤 클래스의 멤버 메소드인 것처럼 호출할 수 있지만 그 클래스의 밖에 선언된 함수다.

확장 함수를 만들려면 추가하려는 함수 이름 앞에 그 함수가 확장할 클래스의 이름을 덧붙이기만 하면 된다. 클래스 이름을 수신 객체 타입(receiver type)이라 부르며, 확장 함수가 호출되는 대상이 되는 값(객체)을 수신 객체(receiver object)라고 부른다.

package strings

fun String.lastChar() : Char = this.get(this.length -1)
// String : 수신 객체 타입
// this : 수신 객체

println("Kotlin".lastChar())
// Prints: n

위 코드에서 this를 생략할 수 있다. 확장 함수 내부에서는 일반적인 인스턴스 메소드의 내부에서와 마찬가지로 수신 객체의 메소드나 프로퍼티를 바로 사용할 수 있다.

하지만 확장 함수가 캡슐화를 깨지는 않는다. 확장 함수는 클래스 내부에서만 사용할 수 있는 private 멤버나 protected 멤버를 사용할 수 없다.

확장 함수가 다른 확장 함수를 호출할 수 있다. 호출하는 쪽에서는 확장 함수와 멤버 메소드를 구분할 수 없다.

임포트와 확장 함수

확장 함수를 정의했다고 해도 자동으로 그 함수를 사용할 수 있지는 않다. 확장 함수를 사용하기 위해서는 그 함수를 다른 클래스나 함수와 마찬가지로 임포트해야만 한다.

import strings.lastChar

val c = "Kotlin".lastChar()

as 키워드를 사용하면 임포트한 클래스나 함수를 다른 이름으로 부를 수 있다.

import strings.lastChar as last

val c = "Kotlin".last()

한 파일 안에서 다른 여러 패키지에 속해있는 이름이 같은 확장 함수를 가져와 사용해야 하는 경우 이름을 바꿔서 임포트하면 이름 충돌을 막을 수 있다. 일반적인 클래스나 함수라면 그 전체 이름(Fullly Qualified Name)을 써도 된다. 하지만 코틀린 문법상 확장 함수는 반드시 짧은 이름을 써야 한다. 따라서 임포트할 때 이름을 바꾸는 것이 확장 함수 이름 충돌을 해결할 수 있는 유일한 방법이다.

확장 함수는 오버라이드할 수 없다.

다음과 같이 View와 그 하위 클래스인 Button이 있다.

open class View {
    open fun click() = println("View clicked")
}

class Button: View() {
    override fun click() = println("Button clicked")
}

View 타입 변수에 Button 타입 변수를 대입한 후, click 함수를 호출하면 Button이 오버라이드한 click이 호출된다.

실행 시점에 객체 타입에 따라 동적으로 호출될 대상 메소드를 결정하는 방식을 동적 디스패치(dynamic dispatch)라고 한다.

반면 컴파일 시점에 알려진 변수 타입에 따라 정해진 메소드를 호출하는 방식은 정적 디스패치(static dispatch)라고 한다.

val view: View = Button()
view.click()

// Prints: Button clicked