변환하지 못한 기존 자바 코드를 사용하기 위해서, 자바 API를 재작성하지 않고도 코틀린이 제공하는 여러 편리한 기능을 사용할 수 있는데 이것이 확장 함수(extenstion function) 가 해주는 역할이다.
확장 함수는 어떤 클래스의 멤버 메소드인 것처럼 호출할 수 있지만 그 클래스의 밖에 선언된 함수다. 문자열의 마지막 문자를 돌려주는 메소드를 추가해보자.
package strings
fun String.lastChar(): Char = this.get(this.length - 1)
fun String.lastChar(): Char = get(length - 1) // this 없이 수신 객체에 접근 가능
확장 함수는 어떤 클래스의 멤버 메소드인 것처럼 호출할 수 있지만 그 클래스의 밖에 선언된 함수다. (문자열의 마지막 문자를 돌려주는 메소드)
클래스 이름을 수신 객체 타입(receiver type) 이라 부르며, 확장 함수가 호출되는 대상이 되는 값을 수신 객체(receiver object) 라고 부른다.
위의 코드에선 String이 수신 객체 타입, this가 수신 객체가 된다.
>> println("Kotlin".lastChar())
n
// String이 수신 객체 타입, "Kotlin"이 수신객체
클래스 안에서 정의한 메소드와 달리 확장 함수 안에서는 클래스 내부에서만 사용할 수 있는 private 멤버나 protected 멤버를 사용할 수 없다. (캡슐화를 깨지는 않는다.)
1. 임포트와 확장 함수
확장 함수를 사용하기 위해서는 그 함수를 다른 클래스나 함수와 마찬가지로 임포트해야 한다.
가끔 이름이 충돌하는 경우가 생기는데, 코틀린에서는 클래스를 임포트할 때와 동일한 구문을 사용해 개별 함수를 임포트할 수 있다.
import strings.lastChar
val c = "Kotlin".lastChar()
import strings.*
val c = "Kotlin".lastChar()
// as 키워드를 사용하면 임포트한 클래스나 함수를 다른 이름으로 부를 수 있다.
import strings.lastChar as last
val c = "Kotlin".last()
2. 자바에서 확장 함수 호출
내부적으로 확장 함수는 수신 객체를 첫 번째 인자로 받는 정적 메소드다. 자바에서 사용하기 위해서는 정적 메소드를 호출하면서 첫 번째 인자로 수신 객체를 넘기기만 하면 된다.
// 확장 함수를 StringUtil.kt 파일에 정의했다면 다음과 같이 호출됨
char c = StringUtilKt.lastChar("Java");
3. 확장 함수로 유틸리티 함수 정의
fun <T> Collection<T>.joinToString( // Collection<T>에 대한 확장 함수를 선언
separator: String = ", ", // 디폴트값 지정
prefix: String = "",
postfix: String = ""
): String {
val result = StringBuilder(prefix);
for((index, element) in this.withIndex()){ // this는 수신객체를 가르킨다.
if(index > 0) result.append(separator)
result.append(element)
}
result.append(postfix)
return result.toString()
}
joinToString을 마치 클래스의 멤버인 것처럼 호출 할 수 있다.
var list = listOf(1, 2, 3)
println(list.joinToString(";", "(", ")"))
var list2 = arrayListOf(1, 2, 3);
println(list2.joinToString(" "))
확장 함수는 단지 정적 메소드 호출에 대한 문법적인 편의(synatic sugar)일 뿐이다. 그래서 클래스가 아닌 더 구체적인 타입을 수신 객체 타입으로 지정할 수도 있다. 문자열의 컬렉션에 대해서만 호출할 수 있는 Join 함수를 정의하고 싶다면 다음과 같이 하면 된다.
fun Collection<String>.join(
separator: String = ", ",
prefix: String = "",
postfix: String = ""
) = joinToString(separator, prefix, postfix)
>> println(listOf("one", "two", "eight").join(" "))
확장 함수가 정적 메소드와 같은 특징을 가지므로, 확장 함수를 하위 클래스에서 오버라이드 할 수는 없다.
4. 확장 함수는 오버라이드할 수 없다.
View와 그 하위 클래스인 Button이 있는데, Button이 상위 클래스의 click 함수를 오버라이드하는 경우
open class View{
open fun click() = println("View clicked")
}
class Button: View(){ // Button은 View를 확장
override fun click() = println("Button clicked");
}
Button이 View의 하위 타입이기 때문에 View 타입 변수를 선언해도 Button 타입 변수를 그 변수에 대입할 수 있다.
>> var view: View = Button()
>> view.click()
Button clicked
확장 함수는 클래스 밖에 선언되므로 클래스의 일부가 아니다. 이름과 파라미터가 완전히 같은 확장 함수를 기반 클래스와 하위 클래스에 대해 정의해도 실제로는 확장 함수를 호출할 때 수신 객체로 지정한 변수의 정적 타입에 의해 어떤 확장함수가 호출될지 결정되지, 그 변수에 저장된 객체의 동적인 타입에 의해 확장 함수가 결정되지 않는다.
View와 Button 클래스에 대해 선언된 두 showOff 확장 함수
fun View.showOff() = println("I'm a view!")
fun Button.showOff() = prtinln("I'm a button!")
// view가 가리키는 객체의 실제 타입은 Button이지만,
// 이 경우 view의 타입이 View이기 때문에 무조건 View의 확장 함수가 호출된다.
>> val view: View = Button()
>> view.showOff();
I'm a view!
/* 자바 */
>> View view = new Button();
>> ExtensionKt.showOff(view);
I'm a view
5. 확장 프로퍼티
확장 프로퍼티를 사용하면 기존 클래스 객체에 대한 프로퍼티 형식의 구문으로 사용할 수 있는 API를 추가할 수 있다. 실제로 확장 프로퍼티는 아무 상태도 가질수 없지만 프로퍼티 문법으로 더 짧게 코드를 작성할 수 있어서 편한 경우가 있다.
lastChar라는 함수를 프로퍼티로 바꿔보자.
val String.lastChar: Char get() = get(length-1)
확장 함수의 경우와 마찬가지로 확장 프로퍼티도 일반적인 프로퍼티와 같은데, 다닞 수신 객체 클래스가 추가됐을 뿐이다. 뒷받침하는 필드 가 없어서 기본 게터구현을 제공할 수 없으므로 최소한 게터는 꼭 정의를 해야 한다. 마찬가지로 초기화 코드에서 계산한 값을 담을 장소가 전혀 없으므로 초기화 코드도 쓸 수 없다.
StringBuilder에 같은 프로퍼티를 정의한다면 StringBuilder의 맨 마지막 문자는 변경 가능하므로 프로퍼티를 var로 만들 수 있다.
var StringBuilder.lastChar: Char
get() = get(length - 1) // 프로퍼티 게터
set(value: Char){
this.setCharAt(legnth -1, value) // 프로퍼티 세터
}
확장 프로퍼티를 사용하는 방법은 멤버 프로퍼티를 사용하는 방법과 같다.
>>> println("Kotlin".lastChar)
n
>>> val sb = StringBuilder("Kotlin?")
>>> sb.lastChar = '!'
>>> println(sb)
Kotlin!
'프로그래밍 노트 > Kotlin' 카테고리의 다른 글
[Kotlin] 코틀린 문자열과 정규식 다루기 (0) | 2020.11.24 |
---|---|
[Kotlin] 코틀린 컬렉션 처리: 가변 길이 인자, 중위 함수 호출, 라이브러리 지원 (0) | 2020.11.19 |
[Kotlin] 코틀린(Kotlin) 함수만들기 (0) | 2020.11.14 |
[Kotlin] 코틀린(Kotlin)에서 컬렉션 만들기 (0) | 2020.11.14 |
[Kotlin] 코틀린(Kotlin) while과 for 루프 (0) | 2020.11.09 |