728x90
반응형

메소드를 다른 클래스에 추가: 확장 함수와 확장 프로퍼티

기존 코드와 코틀린 코드를 자연스럽게 통합하는 것은 코틀린의 핵심 목표 중 하나임
이런 기존 자바 API를 재작성하지 않고도 코틀린이 제공하는 여러 편리한 기능을 사용할 수 는 없을까?

바로 확장 함수가 그런 역할을 해줄 수 있음

개념적으로 확장 함수는 단순함
확장 함수는 어떤 클래스의 멤버 메소드인 것처럼 호출할 수 있지만 그 클래스의 밖에 선언된 함수임
문자열의 마지막 문자를 돌려주는 확장 메소드를 추가해보자
package strings
fun String.lastChar(): Char = this.get(this.length - 1)

확장 함수를 만들려면 추가하려는 함수 이름 앞에 그 함수가 확장할 클래스의 이름을 덧붙이기만 하면 됨

클래스 이름을 수신 객체 타입(receiver type)이라 부르며, 확장 함수가 호출되는 대상이 되는 값(객체)을 수신 객체(receiver object)라고 부름

println("Kotlin".lastChar())

// String = 수신 객체 타입
// "Kotlin" = 수신 객체

확장 함수는 클래스 안에서 정의한 메소드와 달리 확장 함수 안에서는 클래스 내부에서만 사용할 수 있는 비공개(private) 멤버나 보호된(protected) 멤버를 사용할 수 없음


임포트와 확장 함수

확장 함수를 사용하기 위해서는 그 함수를 다른 클래스나 함수와 마찬가지로 임포트해야만 함
확장 함수를 임포트 없이 사용한다면 동일한 이름의 확장 함수와 충돌할 수도 있기 때문에 임포트로 어떤 확장함수인지 명시해 주어야 함
import strings.lastChar // 명시적으로 사용
import strings.* // * 사용 가능
import strings.lastChar as last // as 키워드를 사용 가능

자바에서 확장 함수 호출

자바에서 확장 함수를 사용하기도 편함
정적 메소드를 호출하면서 첫 번째 인자로 수신 객체를 넘기기만 하면 됨
다른 최상위 함수와 마찬가지로 확장 함수가 들어있는 자바 클래스 이름도 확장 함수가 들어있는 파일 이름에 따라 결정됨
따라서 확장 함수를 StringUtil.kt 파일에 정의했다면 다음과 같이 호출할 수 있음
char c = StringUtilKt.lastChar("java");

확장 함수로 유틸리티 함수 정의

joinToString 함수의 최종 버전을 만들어보기
이제 이 함수는 코틀린 하이브러리가 제공하는 함수와 거의 같아졌음
fun <T> Collection<T>.joinToString(
        separator: String = ", ",
        prefix: String = "",
        postfix: String = ""
): String {
    val result = StringBuilder(prefix)

    for ((index, element) in this.withIndex()) {
        if (index > 0) result.append(separator)
        result.append(element)
    }

    result.append(postfix)
    return result.toString()
}

fun main(args: Array<String>) {
    val list = arrayListOf(1, 2, 3)
    println(list.joinToString(" "))
}

확장 함수는 단지 정적 메소드 호출에 대한 문법적인 편의일 뿐임

그래서 클래스가 아닌 더 구체적인 타입을 수신 객체 타입으로 지정할 수도 있음


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

확장 함수는 클래스의 일부가 아님

확장 함수는 클래스 밖에 선언됨

이름과 파라미터가 완전히 같은 확장 함수를 기반 클래스와 하위 클래스에 대해 정의해도 실제로는 확장 함수를 호출할 때 수신 객체로 지정한 변수의 정적 타입에 의해 어떤 확장함수가 호출될지 결정되지, 그 변수에 저장된 객체의 동적인 타입에 의해 확장 함수가 결정되지 않음

더보기

💡 확장 함수 팁

어떤 클래스를 확장한 함수와 그 클래스의 멤버 함수의 이름과 시그니처가 같다면 확장 함수가 아니라 멤버 함수가 호출됨(멤버 함수의 우선순위가 더 높음). 클래스의 API를 변경할 경우 항상 이를 염두에 둬야 함.


확장 프로퍼티

확장 프로퍼티를 사용하면 기존 클래스 객체에 대한 프로퍼티 형식의 구문으로 사용할 수 있는 API를 추가할 수 있음
프로퍼티라는 이름으로 불리기는 하지만 상태를 저장할 적절한 방법이 없기 때문에 실제로 확장 프로퍼티는 아무 상태도 가질 수 없음
val String.lastChar: Char
		get() = get(length -1)

뒷받침하는 필드가 없어서 기본 게터 구현을 제공할 수 없으므로 최소한 게터는 꼭 정의를 해야 함

마찬가지로 초기화 코드에서 계산한 값을 담을 장소가 전혀 없으므로 초기화 코드도 쓸 수 없음

var StringBuilder.lastChar: Char
    get() = get(length - 1)
    set(value: Char) {
        this.setCharAt(length - 1, value)
    }

fun main(args: Array<String>) {
    println("Kotlin".lastChar)
    val sb = StringBuilder("Kotlin?")
    sb.lastChar = '!'
    println(sb)
}
반응형
복사했습니다!