본문 바로가기

코틀린

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

■ 수신 객체를 갖지 않는 함수 타입 : 기존 클래스의 메소드 참조
확장 함수 타입(=수신 객체를 갖는 함수 타입)과 일반 함수 타입은 어떤 차이가 있을까요? 아주 뚜렷한 차이점이 있습니다. 아래 예제에서 쉽게 확인할 수 있습니다. 수신 객체가 없기 때문에 9.intMultiple(7)과 같은 표현은 사용할 수 없습니다.

fun main() {
    val intMultiple: (Int, Int) -> Int = Int::times
    // val intMultiple: (Int, Int) -> Int = { i,j -> i.times(j) } // 람다 식 사용

    println(9.intMultiple(7)) // 에러 발생
    println(intMultiple(9, 7))
}

 

■ 확장 함수를 기존 클래스의 멤버 함수에 포함시켜도 되나요?
3.6.2절(p.151)
 예제에서는 클래스 Student에 대해 확장 함수 isScholarship()을 정의했습니다. 확장 함수도 클래스의 멤버 함수와 마찬가지 방법(Student::isScholarship)으로 참조할 수 있습니다.

class Student(val name: String) {
    fun hasPassed(score: Int): String =
        if (score > 60) "Passed" else "Failed"
}

fun Student.isScholarship(score: Int): Boolean {
    return score > 85
}

fun main() {
    val kim = Student("Kim")
    val status: Student.(Int) -> Boolean = Student::isScholarship
    
    println(kim.hasPassed(78))
    println(kim.status(78))
}

 위 코드를 아래처럼 수정할 수 있습니다. 

fun main() {
    val kim = Student("Kim")
    val status = kim::isScholarship

    println(status(78))
}

잠깐! 아래 코드에서 함수 타입에 주목하세요. KFunction1<Int, Boolean>, 낯선 함수 타입이죠. KFunction은 리플렉션(reflection) API 타입을 가리킵니다. 리플렉션이란 실행시간(runtime)에 객체의 멤버(속성 또는 메소드)에 접근할 수 있는 방법을 말하며, 관련 API를 사용할 수 있도록 import 문을 추가해야 합니다. 숫자 1은 1개의 형식인자가 필요하다는 뜻입니다. 함수 타입 KFunction1<Int, Boolean>은 1개의 Int 타입 형식인자가 필요하며, Boolean 타입을 반환한다는 뜻입니다. 

import kotlin.reflect.KFunction1

fun main() {
    val kim = Student("Kim")
    val status: KFunction1<Int, Boolean> = kim::isScholarship

    println(status(78))
}

 

확장 함수 isScholarship()을 기존 클래스 Student의 멤버 함수로 포함할 수 있을까요? 가능합니다. 멤버 함수와 확장 함수의 차이점은 클래스 이름이 있고 없고의 차이입니다. 그렇다면 굳이 포함시킬 필요가 있을까요? 

class Student(val name: String) {
    fun hasPassed(score: Int): String =
        if (score > 60) "Passed" else "Failed"
    fun Student.isScholarship(score: Int): Boolean {
        return score > 85
    }
}

 

필요할 때가 있습니다. 확장 함수이면서 멤버 함수가 된 isScholarship()은 클래스 Student의 private 멤버에 접근할 수 있는 권한을 갖습니다. isScholarship()은 private 멤버로 선언한 속성 grade에 자유롭게 접근할 수 있습니다. 

class Student(val name: String) {
    private var grade: Char = 'F'
    fun hasPassed(score: Int): String =
        if (score > 60) "Passed" else "Failed"
    fun Student.isScholarship(score: Int): Boolean {
        if (score >= 90) grade = 'A'
        else if (score in 85..89) grade = 'B'
        return score > 85
    }
}