본문 바로가기
Kotlin

[Kotlin in Action] 3.3 메소드를 다른 클래스에 추가: 확장 함수와 확장 프로퍼티

by Nhahan 2025. 3. 14.

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

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

수신 객체 타입은 확장이 정의될 클래스의 타입이며, 수신 객체는 그 클래스에 속한 인스턴스 객체다.

 

일반 메소드의 본문에서 this를 사용할 때와 마찬가지로 확장 함수 본문에도 this를 쓸 수 있다. 그리고 일반 메소드와 마찬가지로 확장 함수 본문에서도 this를 생략할 수 있다.

package strings

fun String.lastChar(): Char = get(length - 1) // 수신 객체 멤버에 this 없이 접근 가능.

하지만 확장 함수가 캡슐화를 깨지는 않는다. 즉, private, protected 멤버에 사용할 수 없다.

 

3.3.1 임포트와 확장 함수

확장 함수도 마찬가지로 사용하기 위해서는 그 함수를 다른 클래스나 함수와 마찬가지로 import해야만 한다.

import strings.lastChar

val c = "Kotlin".lastChar()

 

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

import strings.lastChar as last

val c = "Kotlin".last()

 

3.3.2 자바에서 확장 함수 호출

내부적으로 확장 함수는 수신 객체를 첫 번째 인자로 받는 정적 메소드다.

만약, 확장 함수를 StringUtil.kt 파일에 정의했다면 다음과 같이 호출할 수 있다.

char c = StringUtilKt.lastChar("Java");

 

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

이제 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()
}

>>> val list = listOf(1, 2, 3)
>>> print(list.joinToString(separator = "; ", prefix = "(", postfix = ")"))
(1; 2; 3)

 

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

코틀린에서 확장 함수는 정적이기 때문에 오버라이드 할 수 없다.

 

3.3.5 확장 프로퍼티

확장 프로퍼티를 사용하면 기존 클래스 객체에 대한 프로퍼티 형식의 구문으로 사용할 수 있는 API를 추가할 수 있다. 프로퍼티라는 이름으로 불리기는 하지만 상태를 저장할 적절한 방법이 없기 때문에(기존 클래스의 인스턴스 객체에 필드를 추가할 방법은 없다) 실제로 확장 프로퍼티는 아무 상태도 가질 수 없다. 하지만 프로퍼티 문법으로 더 짧게 코드를 작성할 수 있어서 편한 경우가 있다.

 

앞 절에서 lastChar라는 함수를 정의했다.

fun String.lastChar(): Char = get(length - 1) // 수신 객체 멤버에 this 없이 접근 가능.

이제 이 함수를 프로퍼티로 바꿔보자

val String.lastChar: Char
    get() = get(length - 1)

확장 함수의 경우와 마찬가지로 확장 프로퍼티도 일반적인 프로퍼티와 같은데, 단지 수신 객체 클래스가 추가됐을 뿐이다. 뒷받침하는 필드가 없어서 기본 getter 구현이 없으므로, 최소한 getter는 꼭 정의를 해야한다. 마찬가지로 초기화 코드에서 계산한 값을 담을 장소가 전혀 없으므로 초기화 코드도 쓸 수 없다.

 

StringBuilder에 같은 프로퍼티를 정의한다면 StringBuilder의 맨 마지막 문자는 변경 가능하므로 프로퍼티를 var로 만들 수 있다.

var StringBuilder.lastChar: Char
    get() = get(length - 1) // 프로퍼티 getter
    set(value: Char) {
        this.setCharAt(length - 1, value) // 프로퍼티 setter
    }

확장 프로퍼티를 사용하는 방법은 멤버 프로퍼티를 사용하는 방법과 같다.

>>> val sb = StringBuilder("Kotlin?")
>>> sb.lastChar = '!'
>>> print println(sb)
Kotlin!

 

댓글