Kotlin Extensions
Kotlin Extensions
코틀린의 확장(extension)은 기존 클래스의 소스 코드를 수정하거나 상속하지 않고도 새로운 함수와 프로퍼티를 추가할 수 있는 기능입니다. 컴파일러는 확장 호출을 정적으로 해석(resolve)하며, 실제로 대상 클래스에 멤버를 삽입하지 않습니다. 대신, 수신자를 첫 번째 매개변수로 받는 정적 함수로 컴파일됩니다. 확장은 코틀린의 가장 차별화된 기능 중 하나이며, 강력함과 한계를 모두 이해하는 것이 유지보수하기 좋은 코드를 작성하는 데 필수적입니다. 이번 면접 질문을 통하여 아래 내용들을 학습하실 수 있습니다.
- 확장 함수(extension function)와 확장 프로퍼티(extension property)가 어떻게 컴파일되는지 설명할 수 있습니다.
- 정적 디스패치(static dispatch)가 확장 함수와 멤버 함수 충돌에 어떤 영향을 미치는지 이해할 수 있습니다.
- 안드로이드 개발에서 확장 함수의 실전 활용 사례를 파악할 수 있습니다.
- 대규모 코드베이스에서 확장 함수를 과도하게 사용할 때 발생하는 문제점을 인지할 수 있습니다.
확장 함수 (Extension Functions)
확장 함수는 함수 이름 앞에 수신자 타입(receiver type)을 접두사로 붙여 선언합니다. 함수 본문 내에서 this는 수신 객체를 가리킵니다.
fun Int.isEven(): Boolean {
return this % 2 == 0
}
val result = 4.isEven() // true
컴파일러는 이 코드를 수신자가 첫 번째 매개변수가 되는 정적 메서드로 변환합니다. 디컴파일된 바이트코드를 살펴보면, int 타입의 수신자를 첫 번째 인자로 받고 receiver % 2 == 0의 결과를 반환하는 정적 함수와 동일한 형태입니다. 바이트코드 수준에서 Int 클래스 자체가 변경되지는 않습니다.
이는 곧 확장 함수가 수신자 클래스의 private 또는 protected 멤버에 접근할 수 없다는 것을 의미합니다. 확장 함수는 수신자 타입의 공개(public) API만 사용할 수 있으며, 클래스 내부에 완전히 접근 가능한 멤버 함수와의 중요한 차이점이기도 합니다. 면접에서 이 부분을 명확히 구분하여 답변하면 좋은 인상을 남길 수 있습니다.
확장 프로퍼티 (Extension Properties)
확장 프로퍼티도 동일한 패턴을 따르지만, 상태를 저장할 수 없다는 제약이 있습니다. backing field가 존재하지 않기 때문에, 계산된 getter(그리고 가변 수신자에 대한 setter)만 사용할 수 있습니다.
val String.firstChar: Char
get() = this[0]
val letter = "Hello".firstChar // 'H'
이 코드는 수신자를 받아 계산된 값을 반환하는 정적 함수에 대한 편의 문법(syntactic sugar)입니다. 확장 프로퍼티에서 backing field를 선언하려 하면 컴파일 오류가 발생합니다. 즉, 확장 프로퍼티는 기존 클래스에 저장 공간을 추가하는 것이 아니라, 기존 공개 API를 활용한 계산 로직을 간결하게 표현하는 수단이라고 이해하시면 됩니다.
정적 디스패치와 멤버 우선 규칙 (Static Dispatch and Member Priority)
확장 함수는 변수의 선언 타입(declared type)을 기준으로 정적으로 해석되며, 런타임 타입과는 무관합니다. 만약 클래스에 확장 함수와 동일한 시그니처를 가진 멤버 함수가 존재하면, 멤버 함수가 항상 우선합니다.