1. Extension Function(확장 함수) 개념
- 코틀린은 클래스를 상속하거나 데코레이터와 같은 디자인 패턴을 사용하지 않고도 클래스나 인터페이스에 새로운 기능을 확장할 수 있는 강력한 기능을 제공합니다.
- 이 기능은 '확장(extensions)'이라고 불리는 특별한 선언을 통해 이루어집니다.
- 확장 함수를 사용하면 수정할 수 없는 서드파티 라이브러리의 클래스나 인터페이스에도 새로운 함수를 추가할 수 있습니다.
- 이러한 함수들은 마치 원래 클래스의 메서드인 것처럼 일반적인 방식으로 호출됩니다.
- 확장 함수는 코틀린 표준 라이브러리의 핵심적인 부분으로,
String
,List
,Collection
등 많은 클래스에 풍부한 기능을 제공합니다.
노트
코틀린 확장 함수는 실제로 클래스를 수정하지 않습니다. 대신 해당 타입의 객체에 대해 호출할 수 있는 새로운 함수를 만듭니다. 이는 Open-Closed Principle(개방-폐쇄 원칙)을 자연스럽게 지원하는 방식입니다.
1.1 확장 함수의 장점
- 코드의 간결성: 기존 클래스에 유틸리티 메서드를 추가하여 코드를 더 읽기 쉽고 간결하게 만듭니다.
- 기존 코드 수정 없음: 원본 클래스의 코드를 수정하지 않고도 새로운 기능을 추가할 수 있습니다.
- API 확 장: 라이브러리나 프레임워크 API를 확장하여 특정 도메인이나 프로젝트에 맞게 맞춤화할 수 있습니다.
- 명확한 네임스페이스: 함수는 특정 타입에 연결되어 있어 글로벌 유틸리티 함수보다 발견하기 쉽습니다.
- 명시적인 수신 객체: 코드의 가독성과 명확성을 높입니다.
1.2 확장 프로퍼티
- 확장 함수와 유사하게, 코틀린은 확장 프로퍼티(Extension Properties)도 지원합니다.
- 확장 프로퍼티는 기존 클래스에 새로운 프로퍼티를 추가할 수 있게 해줍니다.
- 실제로는 프로퍼티처럼 보이지만 내부적으로는 getter와 setter를 사용합니다.
val String.lastIndex: Int
get() = this.length - 1
val String.lastChar: Char
get() = this[lastIndex]
var StringBuilder.lastChar: Char
get() = this[this.length - 1]
set(value) {
this.setCharAt(this.length - 1, value)
}
2. 확장 함수 만들기
2.1 기본 문법과 구조
fun 수신타입.함수이름(매개변수): 반환타입 {
// 함수 본문
// 여기서 'this'는 수신 객체를 가리킵니다
}
가장 기본적인 확장 함수 예제로, MutableList<Int>
에 요소 교환 기능을 추가하는 코드를 살펴보겠습니다:
fun MutableList<Int>.swap(index1: Int, index2: Int) {
val tmp = this[index1] // 여기서 'this'는 리스트에 해당합니다
this[index1] = this[index2]
this[index2] = tmp
}
- 확장 함수를 선언하려면, 함수 이름 앞에 '수신 타입(receiver type)'을 붙이는데, 이는 확장하려는 타입을 가리킵니다.
- 위 예시에서
MutableList<Int>
가 수신 타입입니다.
- 위 예시에서
- 확장 함수 내부의 this 키워드는 수신 객체(점 앞에 위치한 객체)에 해당합니다.
val list = mutableListOf(1, 2, 3)
list.swap(0, 2) // 호출 후: [3, 2, 1]
2.2 제네릭을 사용한 확장 함수
특정 타입에 한정되지 않고 여러 타입에 적용할 수 있는 확장 함수를 만들려면 제네릭을 사용합니다:
fun <T> MutableList<T>.swap(index1: Int, index2: Int) {
val tmp = this[index1]
this[index1] = this[index2]
this[index2] = tmp
}
- 이제 어떤 타입의
MutableList
든 swap 함수를 호출할 수 있습니다. - 수신 타입 표현식에서 제네릭 타입 파라미터를 사용하려면 함수 이름 앞에 제네릭 타입 파라미터를 선언해야 합니다.
val numbers = mutableListOf(1, 2, 3)
numbers.swap(0, 2) // [3, 2, 1]
val names = mutableListOf("Alice", "Bob", "Charlie")
names.swap(0, 2) // [Charlie, Bob, Alice]
2.3 확장 함수 제약 조건 설정
타입 제약을 사용하여 특정 조건을 만족하는 타입에만 확장 함 수를 적용할 수도 있습니다:
// Comparable을 구현하는 타입에만 적용되는 확장 함수
fun <T : Comparable<T>> MutableList<T>.sortIfNotEmpty() {
if (this.isNotEmpty()) {
this.sort()
}
}