728x90
반응형
✅ 메서드를 다른 클래스에 추가 : 확장 함수와 확장 프로퍼티
- 기존 코드와 코틀린 코드를 자연스럽게 통합하는 것은 코틀린의 핵심 목표 중 하나
- 기존 자바 API를 재작성하지 않고도 코틀린이 제공하는 여러 편리한 기능을 사용할 수 있는 방법은??
확장 함수의 활용
- 개념적으로 확장 함수는 단순함
- 확장 함수는 어떤 클래스의 멤버 메서드인 것처럼 호출할 수 있지만 그 클래스의 밖에 선언된 함수
- 확장 함수를 보여주기 위한 어떤 문자열의 마지막 문자를 돌려주는 메서드
package strings
fun String.lastChar() : Char = this.get(this.length - 1)
- 확장 함수를 만들기 위해선 추가하려는 함수 이름 앞에 그 함수가 확장할 클래스의 이름을 덧붙이기만 하면 됨
- 클래스 이름을 수신 객체 타입(Receiver Type)이라 부르며, 확장 함수가 호출되는 대상이 되는 값을 수신 객체(Receiver Object)라 부름
fun String.lastChar() : Char = this.get(this.length - 1)
// String : 수신 객체 타입
// this : 수신 객체
--------------------------------------------
println("Kotlin".lastChar()) // n
// String : 수신 객체 타입
// "Kotlin" : 수신 객체
- 확장 함수 내부에서는 일반적인 인스턴스 메서드의 내부에서와 마찬가지로 수신 객체의 메서드나 프로퍼티를 바로 사용할 수 있음
- 하지만 확장 함수가 캡슐화를 깨는 것은 아님
- 클래스 안에서 정의한 메서드와 달리 확장 함수 안에서는 클래스 내부에서만 사용할 수 있는 비공개(Private) 멤버나 보호된(Protected) 멤버를 사용할 수 없음
📌 임포트와 확장 함수
- 확장 함수를 사용하기 위해서는 그 함수를 다른 클래스나 함수와 마찬가지로 임포트해야만 함
- 임포트 없이 사용하게 되는 경우 동일한 이름의 확장 함수와 충돌할 수 있기 때문에 임포트하여 명시해주어야 함
- 코틀린에서는 클래스를 임포트할 때와 동일한 구문을 사용해 개별 함수를 임포트할 수 있음
import strings.lastChar
val c = "Kotlin".lastChar()
-----------------------------
import strings.*
val c = "Kotlin".lastChar()
-----------------------------
import strings.lastChar as last // Python과 비슷하게 as 키워드를 사용하여 줄여 쓸 수 있음
val c = "Kotlin".last();
📌 자바에서 확장 함수 호출
- 자바에서 코틀린의 확장 함수를 사용하기 편함
- 내부적으로 확장 함수는 수신 객체를 첫 번째 인자로 받는 정적 메서드이기 때문에 메서드 호출 시 첫 번째 인자로 수신 객체를 넘기기만 하면 됨
- 다른 최상위 함수와 마찬가지로 확장 함수가 들어있는 자바 클래스 이름도 확장 함수가 들어있는 파일 이름에 따라 결정됨
- 따라서 확장 함수를 StringUtil.kt 파일에 정의했다면 아래와 같이 호출할 수 있음
/* 자바 */
char c = StringUtilKt.lastChar("Java");
📌 확장 함수로 유틸리티 함수 정의
- 이제 joinToString 함수의 최종 버전을 아래와 같이 작성할 수 있음
// joinToString()를 확장으로 정의하기
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"는 수신 객체를 가리키며 여기서는 T 타입의 원소로 이뤄진 컬렉션
if(index > 0) result.append(separator)
result.append(element)
}
result.append(postfix)
return result.toString()
}
val list = arrayListOf(1, 2, 3)
println(list.joinToString(" ")) // 1 2 3
- 확장 함수는 단지 정적 메서드 호출에 대한 문법적인 편의일 뿐임
- 그래서 클래스가 아닌 더 구체적인 타입을 수신 객체 타입으로 지정할 수 있음
- 따라서 문자열의 컬렉션에 대해서만 호출할 수 있는 join 함수를 정의하고 싶은 경우 아래와 같이 작성하면 됨
fun Collection<String>.join (
separator : String = ", ",
prefix : String = "",
postfix : String = ""
) = joinToString(separator, prefix, postfix)
println(listOf("one", "two", "eight").join(" ")) // one two eight
- 또한, 확장 함수가 정적 메서드와 같은 특징을 가지므로, 확장 함수를 하위 클래스에서 오버라이드 할 수 없음
정적 메서드의 특징
1. 정적 메서드는 메서드가 정의된 클래스에 속해 있는 것으로 취급되며, 객체를 생성하여 호출할 필요 없이 클래스명을 통해 참조 및 호출할 수 있음
2. 같은 클래스를 통해 생성된 객체들 간 같은 코드를 사용하는 것을 보장하기 위해 사용
3. 정적 메서드는 오버라이드 될 수 없음. 컴파일 과정에서 정적 바인딩되어 메서드 타입이 정해짐
- 부모클래스, 자식클래스 간 같은 이름의 메서드를 정의할 수 있지만 항상 상위 클래스의 정적 메서드만 호출됨
📌 확장 함수는 오버라이드할 수 없음
- 확장 함수는 클래스의 일부가 아니며, 클래스 밖에 선언됨
- 이름과 파라미터가 완전히 같은 확장 함수를 기반 클래스와 하위 클래스에 대해 정의해도 실제로는 확장 함수를 호출할 때 수신 객체로 지정한 변수의 정적 타입에 의해 어떤 확장 함수가 호출될지 결정될뿐, 그 변수에 저장된 객체의 동적인 타입에 의해 확장 함수가 결정되지 않음
어떤 클래스를 확장한 함수와 그 클래스의 멤버 함수의 이름과 시그니처가 같다면 확장 함수가 아니라 멤버 함수가 호출됨(멤버 함수의 우선순위가 더 높기 때문).
클래스의 API를 변경할 경우 항상 이를 염두에 둬야 함.
확장 함수와 이름과 시그니처가 같은 멤버 함수를 여러분의 클래스 내부에 추가하면 클라이언트 프로젝트를 재컴파일한 순간부터 그 클라이언트는 확장 함수가 아닌 새로 추가된 멤버 함수를 사용하게 됨.
📌 확장 프로퍼티
- 확장 프로퍼티를 사용하면 기존 클래스 객체에 대한 프로퍼티 형식의 구문으로 사용할 수 있는 API를 추가할 수 있음
- 프로퍼티라는 이름으로 불리지만 상태를 저장할 적절한 방법이 없기 때문에 실제로 확장 프로퍼티는 아무 상태도 가질 수 없음
- 하지만 프로퍼티 문법으로 더 짧게 코드를 작성할 수 있어 편리함
// 확장 프로퍼티 선언하기
var String.lastChar : Char
get() = get(length - 1)
- 확장 함수의 경우와 마찬가지로 확장 프로퍼티도 일반적인 프로퍼티와 같지만, 단지 수신 객체 클래스가 추가됐을 뿐임
- 하지만 뒷받침하는 필드가 없기 때문에 기본 게터 구현을 제공할 수 없어, 최소한의 게터는 꼭 정의를 해야 함
- 마찬가지로 초기화 코드에서 계산한 값을 담을 장소가 전혀 없기 때문에 초기화 코드도 쓸 수 없음
// 변경 가능한 확장 프로퍼티 선언하기
var StringBuilder.lastChar : Char
get() = get(length - 1) // 프로퍼티 게터
set(value : Char) {
this.setCharAt(length - 1, value) // 프로퍼티 세터
}
- 확장 프로퍼티를 사용하는 방법은 멤버 프로퍼티를 사용하는 방법과 같음
println("Kotlin".lastChar) // n
val sb = StringBuilder("Kotlin?")
sb.lastChar = '!'
println(sb) // Kotlin!
- 자바에서 확장 프로퍼티를 사용하고 싶다면 항상 StringUtilKt.getLastChar("Java")처럼 게터나 세터를 명시적으로 호출해야 함
728x90
반응형
'Studying > Kotlin' 카테고리의 다른 글
[Kotlin In Action] 3장. 함수 정의와 호출(4) - 문자열과 정규식 다루기 (0) | 2023.05.08 |
---|---|
[Kotlin In Action] 3장. 함수 정의와 호출(3) - 컬렉션 처리 (0) | 2023.05.08 |
[Kotlin In Action] 3장. 함수 정의와 호출(1) - 컬렉션과 함수 만들기 (0) | 2023.05.02 |
[Kotlin In Action] 2장. 코틀린 기초(4) - 코틀린의 예외 처리 (0) | 2023.04.24 |
[Kotlin In Action] 2장. 코틀린 기초(3) - while과 for 루프 (0) | 2023.04.24 |