Skip to content

중급: 확장 함수

이 챕터에서는 코드를 더욱 간결하고 읽기 쉽게 만들어주는 Kotlin의 특별한 함수들을 살펴봅니다. 이 함수들이 효율적인 디자인 패턴을 사용하여 프로젝트를 다음 단계로 끌어올리는 데 어떻게 도움이 되는지 알아보세요.

확장 함수

소프트웨어 개발에서는 원본 소스 코드를 변경하지 않고 프로그램의 동작을 수정해야 하는 경우가 많습니다. 예를 들어, 프로젝트에서 서드파티 라이브러리의 클래스에 추가 기능을 더하고 싶을 수 있습니다.

확장 함수는 클래스에 추가 기능을 부여하여 확장할 수 있도록 해줍니다. 확장 함수는 클래스의 멤버 함수를 호출하는 것과 동일한 방식으로 호출합니다.

확장 함수의 문법을 소개하기 전에 **리시버 타입(receiver type)**과 **리시버 객체(receiver object)**라는 용어를 이해해야 합니다.

리시버 객체는 함수가 호출되는 대상입니다. 다시 말해, 리시버는 정보가 공유되는 위치 또는 대상입니다.

An example of sender and receiver

이 예시에서 main() 함수는 .first() 함수를 호출합니다. .first() 함수는 readOnlyShapes 변수에 대해 호출되므로, readOnlyShapes 변수가 리시버입니다.

리시버 객체는 컴파일러가 언제 함수를 사용할 수 있는지 이해하도록 타입을 가집니다.

이 예시는 표준 라이브러리의 .first() 함수를 사용하여 목록의 첫 번째 요소를 반환합니다. 자신만의 확장 함수를 생성하려면, 확장하려는 클래스 이름 뒤에 .과 함수 이름을 작성하세요. 이어서 인자와 반환 타입을 포함한 나머지 함수 선언을 작성하면 됩니다.

예시:

kotlin
fun String.bold(): String = "<b>$this</b>"

fun main() {
    // "hello"는 리시버 객체입니다.
    println("hello".bold())
    // <b>hello</b>
}

이 예시에서:

  • String은 확장된 클래스이며, 리시버 타입으로도 알려져 있습니다.
  • bold는 확장 함수의 이름입니다.
  • .bold() 확장 함수의 반환 타입은 String입니다.
  • String의 인스턴스인 "hello"는 리시버 객체입니다.
  • 리시버 객체는 본문 내에서 키워드: this를 통해 접근할 수 있습니다.
  • 문자열 템플릿($ 구문)은 this의 값에 접근하는 데 사용됩니다.
  • .bold() 확장 함수는 문자열을 받아 텍스트를 굵게 표시하는 <b> HTML 요소로 감싸서 반환합니다.

확장 지향 설계

확장 함수는 어디에서나 정의할 수 있으며, 이를 통해 확장 지향 설계를 만들 수 있습니다. 이러한 설계는 핵심 기능을 유용하지만 필수적이지 않은 기능과 분리하여 코드를 더 쉽게 읽고 유지보수할 수 있게 합니다.

좋은 예시로는 Ktor 라이브러리의 HttpClient 클래스가 있습니다. 이 클래스는 네트워크 요청을 수행하는 데 도움을 줍니다. 핵심 기능은 HTTP 요청에 필요한 모든 정보를 받는 단일 함수 request()입니다.

kotlin
class HttpClient {
    fun request(method: String, url: String, headers: Map<String, String>): HttpResponse {
        // Network code
    }
}

실제로 가장 많이 사용되는 HTTP 요청은 GET 또는 POST 요청입니다. 이러한 일반적인 사용 사례에 대해 라이브러리에서 더 짧은 이름을 제공하는 것이 합리적입니다. 하지만 이러한 요청은 새로운 네트워크 코드를 작성할 필요 없이, 특정 요청 호출만으로 충분합니다. 즉, 이들은 별도의 .get().post() 확장 함수로 정의하기에 완벽한 후보입니다.

kotlin
fun HttpClient.get(url: String): HttpResponse = request("GET", url, emptyMap())
fun HttpClient.post(url: String): HttpResponse = request("POST", url, emptyMap())

.get().post() 함수는 올바른 HTTP 메서드를 사용하여 request() 함수를 호출하므로 직접 호출할 필요가 없습니다. 이 함수들은 코드를 간소화하고 이해하기 쉽게 만듭니다.

kotlin
class HttpClient {
    fun request(method: String, url: String, headers: Map<String, String>): HttpResponse {
        println("Requesting $method to $url with headers: $headers")
        return HttpResponse("Response from $url")
    }
}

fun HttpClient.get(url: String): HttpResponse = request("GET", url, emptyMap())

fun main() {
    val client = HttpClient()

    // request()를 직접 사용하여 GET 요청 수행
    val getResponseWithMember = client.request("GET", "https://example.com", emptyMap())

    // get() 확장 함수를 사용하여 GET 요청 수행
    val getResponseWithExtension = client.get("https://example.com")
}

이러한 확장 지향적 접근 방식은 Kotlin의 표준 라이브러리와 다른 라이브러리에서 널리 사용됩니다. 예를 들어, String 클래스에는 문자열 작업을 돕기 위한 많은 확장 함수가 있습니다.

확장 함수에 대한 자세한 내용은 확장(Extensions)을 참조하세요.

연습

연습 1

정수를 받아 양수인지 확인하는 isPositive라는 확장 함수를 작성하세요.

|---|---|

kotlin
fun Int.// 여기에 코드를 작성하세요.

fun main() {
    println(1.isPositive())
    // true
}

|---|---|

kotlin
fun Int.isPositive(): Boolean = this > 0

fun main() {
    println(1.isPositive())
    // true
}

연습 2

문자열을 받아 소문자 버전으로 반환하는 toLowercaseString이라는 확장 함수를 작성하세요.

힌트

String 타입의 .lowercase() 함수를 사용하세요.

|---|---|

kotlin
fun // 여기에 코드를 작성하세요.

fun main() {
    println("Hello World!".toLowercaseString())
    // hello world!
}

|---|---|

kotlin
fun String.toLowercaseString(): String = this.lowercase()

fun main() {
    println("Hello World!".toLowercaseString())
    // hello world!
}

다음 단계

중급: 스코프 함수