[출처 - Kotlin In Action] [아래 내용들은 Kotlin In Action을 공부하면서 스스로 정리한 내용입니다]
확장함수
kotlin에는 확장함수라는 기능이 있습니다. 확장함수는 클래스의 멤버 메소드인 것 처럼 호출 할 수 있지만 그 클래스 밖에 선언된 함수 입니다.
그럼 예제로 확장 함수를 알아 봅시다. 마지막 글자를 획득하는 확장 함수를 만들어 보겠습니다.
확장 함수를 만드려면 추가하려는 함수 이름 앞에 그 함수가 확장할 클래스의 이름을 덧붙이면 됩니다.
fun main(args: Array<String>) {
println("Kotlin".lastChar())
}
fun String.lastChar():Char = this.get(this.length-1)
위 code에서 String.lastChar()이 확장 함수 입니다. 저 code를 통하여 확장함수에 대하여 설명을 드리면
fun String.lastChar():Char = this.get(this.length-1)
에서 String이 수신 객체 타입(위 설명에서 클래스 이름입니다.), this가 수신객체(확장 함수가 호출되는 대상이 되는 값)를 의미합니다. 그래서 String이 수신객체 타입이기 때문에 "Kotlin"이 수신객체가 될 수 있습니다.
그리고 일반 메소드의 본문에서 this를 사용할 때와 마찬가지로 확장함수 본문에서도 this를 사용가능하고, 일반 메소드에서 this를 생략할 수 있는 것 처럼 확장함수 본문에서도 this를 생략 할 수 있습니다.
fun String.lastChar():Char = get(length-1)
또한, 클래스 안에서 정의한 메소드와 달리 확장 함수 안에서는 클래스 내부에서만 사용할 수 잇는 비공개(private) 멤버나 보호된(protected) 멤버를 사용할 수 없습니다.
확장함수 호출
확장 함수를 사용하려면 파일 내부에서는 따로 설정 없이 사용할 수 있지만, 그 파일 이외의 동일 프로젝트 안이라도 사용할 수 있는 것은 아닙니다.
사용을 위해서는 임포트가 필요 합니다. code를 통해 확인해 보겠습니다.
ExtentionFun.kt라는 파일을 만들고 다음과 같이 작성합니다.
package strings
fun String.lastChar():Char = get(length-1)
그리고 확장함수 lastChar을 사용할 파일에 들어가서 다음과 같이 작성합니다.(저는 Test_ExtentionFun.kt 라는 파일을 만들었습니다.)
import strings.*
fun main(args: Array<String>) {
println("Kotlin".lastChar())
}
import strings.*을 통하여 확장 함수를 사용할 수 있도록 합니다. .*을 하면 pakage strings에 있는 함수들을 사용할 수 있습니다. import strings.* 말고도 import strings.lastChar를 사용해도 확장함수 lastChar() 을 사용할 수 있고 또한
import strings.lastChar as last
fun main(args: Array<String>) {
println("Kotlin".last())
}
위와 같이 as last를 붙여서 lastChar을 last로 변경하여 사용이 가능합니다. 이렇게 하는 이유는 확장함수의 이름 충돌을 피하는 방법입니다.
Java에서는
import strings.ExtentionFunKt;
public class Test_ExtentionFun {
public static void main(String[] args){
char c = ExtentionFunKt.lastChar("kotlin");
}
}
이렇게 사용할 수 있습니다.
확장함수로 유틸리티 함수 정의
그럼 이전에 만든 joinToString 함수를 이용하여 확장함수에 대해서 더 확인해 보겠습니다. (joinToString 함수는 여기서 확인 할 수 있습니다.)
joinToString의 Collection<T>을 수신타입으로 확장함수를 만들면
package extention_test
fun <T> Collection<T>.joinToString(
separator: String = ", ",
prefix: String = "",
postfix: String = ""
): String {
val result = StringBuilder(prefix)
for ((index, element) in this.withIndex()) {
if (index > 0) result.append(separator)
result.append(element)
}
result.append(postfix)
return result.toString()
}
fun main(args: Array<String>) {
val list = arrayListOf(1, 2, 3)
println(list.joinToString(" "))
}
이렇게 확장함수를 만들어 사용할 수 있습니다. 이런 확장함수는 단지 정적 메소드 호출에 대한 문법적인 편의일 뿐입니다. 그래서 다음과 같이 사용도 가능 합니다.
import extention_test.joinToString
fun Collection<String>.join(
separator: String = ", ",
prefix: String = "",
postfix: String = ""
) = joinToString(separator, prefix, postfix)
fun main(args: Array<String>) {
println(listOf("one", "two", "eight").join(" "))
}
위에서 사용된 joinToString은 그 위에서 작성한 Collection<T>를 수신타입으로 하는 확장함수입니다. 그리고 위에서 Collection<String>으로 하였기 때문에 만일 listOf(1,2,3)을 할 경우 Type mismatch라고 code에 error가 발생합니다.(만일 kotlin REPL에서 실행을 하면 error: type mismatch: inferred type is List but Collection was expected 라는 결과를 볼 수 있습니다.)
확장함수는 오버라이드 할 수 없다
자 다음 코드를 보겠습니다.
open class View {
open fun click() = println("View clicked")
}
class Button: View() {
override fun click() = println("Button clicked")
}
fun main(args: Array<String>) {
val view: View = Button()
view.click()
}
Button이 View를 상속 받아 Button은 View의 자식 클래스가 됩니다. 그래서 위의 main에서 처럼 view에 Button 을 대입할 수 있습니다. 그리고 실행을 하면
Button clicked
View 타입의 click이 실행될 것 같지만 Button이 View의 click을 오버라이드 했고 view는 Button으로 생성되었기 때문에 Button의 click이 동작합니다.
하지만 확장함수는 이런 오버라이드를 사용 할 수 없습니다. 다음을 보겠습니다.
open class View {
open fun click() = println("View clicked")
}
class Button: View() {
override fun click() = println("Button clicked")
}
fun View.showOff() = println("I'm a view!")
fun Button.showOff() = println("I'm a button!")
fun main(args: Array<String>) {
val view: View = Button()
view.showOff()
}
위의 View와 Button이외에 showOff라는 확장함수를 각각을 수신타입으로하여 생성하였습니다. 그리고 main에서 view를 Button으로 생성 후 showOff를 실행 시키면
I'm a view!
라는 결과가 나옵니다. 만일 main을
fun main(args: Array<String>) {
val view: Button = Button()
view.showOff()
}
이렇게 바꾸면 I'm a button! 이라는 결과가 나올 것입니다. 그 이유는 확장함수는 클래스의 일부가 아니고 클래스 밖에 선언됩니다. 그래서 수신객체로 지정한 변수의 정적 타입에 의해 어떤 확장 함수가 호출 될지 결정됩니다. 그래서 첫번쨰 main에서는 수신객체 타입이 View여서 I'm a view!라는 결과가 두번째 main 에서는 수신 객체의 타입이 Button이어서 I'm a button!이라는 결과가 나타났습니다.
그리고 어떤 클래스를 확장한 함수와 그 클래스의 멤버 함수의 이름과 시그니처가 같다면 확장함수가 아니라 멤버 함수가 호출됩니다(멤버 함수의 우선순위가 높습니다)
확장 프로퍼티
확장 프로퍼티를 사용하면 기존 클래스 객체에 대한 프로퍼티 형식의 구문으로 사용할 수 있는 API를 추가할 수 있습니다. 사실 확장 프로퍼티는 아무런 상태를 가질 수 없지만, 프로퍼티 문법으로 더 짧게 코드를 작성할 수 있습니다. ( 사실 글로는 무슨 뜻인지 확 와닿지가 않는다. 그러니 코드로 확인해보게습니다.)
val String.lastChar: Char
get() = get(length - 1)
var StringBuilder.lastChar: Char
get() = get(length - 1)
set(value: Char) {
this.setCharAt(length - 1, value)
}
다음과 같이 String과 StringBuilder에 대한 확장 프로퍼티를 만들었습니다. 위에서 보면 알 수 있듯이, 일반 프로퍼티와 같지만 확장함수처럼 수신객체가 추가 되었습니다. 이런 확장 프로퍼티는 기본적으로 getter는 꼭 구현해야 합니다. 또한, 초기화 코드에서 계산한 값을 담을 장소가 없으므로 초기화 코드도 쓸 수 없습니다.
그럼 위의 확장 프로퍼티를 사용하면
val String.lastChar: Char
get() = get(length - 1)
var StringBuilder.lastChar: Char
get() = get(length - 1)
set(value: Char) {
this.setCharAt(length - 1, value)
}
fun main(args: Array<String>) {
println("Kotlin".lastChar)
val sb = StringBuilder("Kotlin?")
sb.lastChar = '!'
println(sb)
}
n
Kotlin!
이런 결과를 확인 할 수 있습니다.
그리고 자바에서 사용하려면
import ExtensionProperties.ExtensionPropertiesKt;
public class Test_Join {
public static void main(String[] args){
char c = ExtensionPropertiesKt.getLastChar("Java");
}
}
이렇게 게터나 세터를 명시적으로 표현해 주어야 합니다. (당연히 위에서 확장프로터티를 가진 파일 이름은 ExtensionProperties.kt이고 pakege는 ExtensionProperties 입니다.)
'2023년 이전 > kotlin' 카테고리의 다른 글
kotlin - 문자열과 정규식 다루기 (0) | 2020.02.20 |
---|---|
kotlin - 컬렉션 처리(가변 길이 인자, 중위 함수 호출, 라이브러리 지원) (0) | 2020.02.20 |
Kotlin - 함수를 호출하기 쉽게 만들기 (0) | 2020.02.19 |
코틀린(Kotlin)- 코틀린에서 컬렉션 만들기 (0) | 2020.02.19 |
Kotlin - let, apply, run, with, also (0) | 2019.11.20 |