Extension functions(확장 함수)는 기존에 정의된 클래스에 함수를 추가하는 기능입니다. 자신이 만든 클래스는 새로운 함수가 필요할 때 쉽게 추가할 수 있는데요. Standard Library 또는 다른 사람이 만든 라이브러리를 사용할 때 함수를 추가하기가 매우 어렵습니다.
예를들어, 1부터 6까지의 숫자가 담긴 List가 있고, 이 List에서 3보다 큰 숫자를 찾아 출력해주는 프로그램을 만들려고 합니다.
List 클래스는 toString()
메소드를 지원하기 때문에 출력은 쉽습니다. 하지만 3보다 큰 숫자를 찾아주는 메소드는 없습니다.
아래 코드처럼 List에서 getHigherThan(num: Int)
라는 메소드를 지원해주면 코드는 간결해지고 가독성도 좋아질 수 있습니다.
fun main() {
val numbers: List<Int> = listOf(1, 2, 3, 4, 5, 6)
val filtered = numbers.getHigherThan(3).toString()
System.out.println(filtered)
}
확장 함수를 정의하는 방법
코틀린은 클래스에 함수를 확장하는 Extension functions(확장함수)를 제공하기 때문에 List 클래스에 getHigherThan()
함수를 추가할 수 있습니다.
확장 함수는 fun 클래스이름.함수이름(인자타입): 리턴타입 { 구현부 }
으로 정의할 수 있습니다.
아래코드는 확장함수를 이용하여 ListgetHigherThan()
를 추가한 코드입니다.
fun List<Int>.getHigherThan(num: Int): List<Int> {
val result = arrayListOf<Int>()
for (item in this) {
if (item > num) {
result.add(item)
}
}
return result
}
fun main() {
val numbers: List<Int> = listOf(1, 2, 3, 4, 5, 6)
val filtered = numbers.getHigherThan(3).toString()
System.out.println(filtered)
}
함수를 추가하려는 클래스는 List<Int>
이고, 함수 이름은 getHigherThan
입니다. 함수의 인자는 num: Int
이고, 리턴타입은 List<Int>
입니다. 그래서 fun List<Int>.getHigherThan(num: Int): List<Int>
이렇게 함수를 정의하였습니다. 함수의 구현부에서 this
가 사용되고 있는데요. this는 객체의 자신입니다. 만약 numbers.getHigherThan(3)처럼 함수가 호출되었다면 구현부 안에서의 this는 numbers 객체를 의미합니다.
위 코드를 실행하면, 의도한 것처럼 3보다 큰 숫자들만 출력됩니다.
[4, 5, 6]
Generic class의 확장 함수
위에서는 Listfun <E> 클래스이름<E>.함수이름(인자타입): 리턴타입 { 구현부 }
로 정의할 수 있습니다.
위의 List
fun <E> List<E>.getHigherThan(num: E): List<E> {
// 구현....
return arrayListOf<E>()
}
다른 방식으로 함수를 확장하는 방법
확장함수의 장점은 클래스에 필요한 함수를 추가하여 가독성, 구조적인 간결성 등을 향상시킬 수 있습니다. 자바에서는 이런 문법적인 기능을 제공하지 않아, 기존 클래스를 상속받아 새로운 클래스를 재정의하거나 클래스 내에 Composition 관계로 객체를 갖고 새로운 기능을 확장하였습니다. 직접 구현하는 방식은 우선 코드양이 많아지며, final 클래스인 경우 상속을 받을 수 없다는 문제가 있습니다.
다른 방법으로, Helper나 Utils 함수를 만들어 기능을 확장할 수 있습니다. 이 부분은 코틀린의 함수 확장과 비슷하지만 가독성이 떨어지는 단점이 있습니다.
함수를 이용하여 함수 확장
가장 간단한 방법은 num과 List를 모두 받는 getHigherThan 함수를 만들고 그 안에 구현할 수 있습니다. 아래 코드처럼 구현할 수 있는데, 단점은 가독성이 떨어진다는 점입니다.
fun getHigherThan(num: Int, list: List<Int>) : List<Int> {
val result = arrayListOf<Int>()
list.forEach { it ->
if (it > num) {
result.add(num)
}
}
return result
}
fun main() {
val numbers: List<Int> = listOf(1, 2, 3, 4, 5, 6)
val filtered = getHigherThan(3, numbers).toString()
System.out.println(filtered)
}
코드를 읽는 사람은 getHigherThan를 먼저 읽고 그 인자에 어떤 리스트가 들어가야 하는지 확인해야 합니다. 또, 인자가 두개이기 때문에 인자들이 어떤 역할을 하는지도 확인해야 하죠.
상속을 이용하여 함수 확장
상속을 이용하여 구현해보면 아래처럼 구현할 수 있습니다.
numbers를 초기화하는 방법이 깔끔하진 않지만 ArrayList<Int>
를 상속한 새로운 클래스를 만들고 거기에 함수를 추가할 수 있습니다.
class NewList : ArrayList<Int>() {
fun getHigherThan(num: Int): List<Int> {
val result = arrayListOf<Int>()
this.forEach { it->
if (it > num) {
result.add(it)
}
}
return result
}
}
fun main() {
val numbers = NewList()
numbers.add(1)
numbers.add(2)
numbers.add(3)
numbers.add(4)
numbers.add(5)
numbers.add(6)
val filtered = numbers.getHigherThan(3).toString()
System.out.println(filtered)
}
상속을 통해서 확장한 함수는 NewList 안에 있기 때문에 main의 코드를 수정해야 합니다. 여기서 List를 NewList로 변경해주었습니다. 하지만 코틀린의 확장함수는 기존 클래스에 함수를 추가하기 때문에 기존 코드를 고칠 필요는 없습니다.
Composition을 이용한 함수 확장
Composition(합성 관계)을 이용한 방법도 상속과 유사합니다. 새로운 클래스 안에 기존 클래스를 내부 객체로 갖고 있고, 새로운 함수를 정의하면 됩니다. 대신 add와 같은 함수들도 추가로 구현을 해야 합니다. 대신에 상속보다는 유연한 구조가 됩니다.
class NewList {
private val list = ArrayList<Int>()
fun add(num: Int) {
list.add(num)
}
fun getHigherThan(num: Int): List<Int> {
val result = arrayListOf<Int>()
list.forEach { it->
if (it > num) {
result.add(it)
}
}
return result
}
}
fun main() {
val numbers = NewList()
numbers.add(1)
numbers.add(2)
numbers.add(3)
numbers.add(4)
numbers.add(5)
numbers.add(6)
val filtered = numbers.getHigherThan(3).toString()
System.out.println(filtered)
}
정리
먼저 코틀린의 확장 함수(Extension functions)에 대해서 알아보았습니다. 그리고 확장 함수를 사용하지 않고 상속과 Composition을 이용하여 함수를 확장해보았습니다. 이렇게 구현해봄으로써, 확장 함수가 주는 장점에 대해서 알아볼 수 있었습니다.
참고
Related Posts
- Kotlin - 배열에서 최소 값, 최대 값 찾기
- Kotlin - 2차원 배열 선언, 초기화 방법
- Kotlin - 배열 선언, 초기화 방법
- Kotlin - 리스트, 배열 길이 가져오기
- Kotlin - 리스트에서 최대, 최소 값 찾기
- Kotlin - for 반복문, 배열/리스트 순회
- Kotlin - Timer, 주기적으로 함수 실행
- Kotlin - sleep, 쓰레드 몇 초 지연
- Kotlin - Thread 생성 및 실행
- Kotlin에서 정규표현식 사용하기
- Kotlin - 문자열 길이 계산
- Kotlin - 문자열 비교 방법(equals, ==, compareTo)
- Kotlin - 2개의 배열 하나로 합치기
- Kotlin - 2개의 List 하나로 합치기
- Kotlin - 디렉토리의 모든 파일 리스트 출력
- Kotlin - 리스트 정렬 방법 (sort, sortBy, sortWith)
- Kotlin - 문자열 뒤집기 (Reverse String)
- Kotlin - 랜덤 숫자 생성 (Random, SecureRandom)
- Kotlin - Range, 숫자 범위 표현
- Kotlin - 음수를 양수로 변환, math.abs()
- Kotlin - List를 Set로 변환
- Kotlin - Set를 List로 변환
- Kotlin - 문자열에서 숫자(int)만 추출하는 방법
- Kotlin - Map을 List로 변환하는 방법
- Kotlin - File, Directory가 존재하는지 확인
- Kotlin - List를 Map으로 변환
- Kotlin - List의 중복 요소 제거
- Kotlin - List를 Array로 변환
- Kotlin - 엘비스 연산자 (Elvis Operation)
- Kotlin - Array를 List로 변환
- Kotlin - String을 Float으로 변환
- Kotlin - String을 Double으로 변환
- Kotlin - String을 Int로 변환
- Kotlin - String을 Long으로 변환
- Kotlin - String Null 또는 Empty 체크