1. 요약
🧑💻: Kotlin Extension을 아시나요?
👨🏻🦱: 네. 상속이나 디자인 패턴 없이 클래스를 간단하게 확장할 수 있는 방법입니다.
실제로 클래스 내부에 메서드나 프로퍼티가 생성되는 것은 아니며,
정적 바인딩 된다는 특징이 있습니다.
(정적바인딩 vs 동적바인딩)
https://jtm0609.tistory.com/215
2. 확장(Extension)
내가 만든 클래스에 새로운 기능을 추가하고 싶을 때 어떻게 해야 할까??
클래스 안에 새로운 메서드를 구현하거나 아니면 상속을 받아서 사용하는 등 여러 방법이 있을 것입니다.
그렇다면 남이 만든 클래스에 새로운 기능을 추가하고 싶으면 어떻게 해야 할까요??
예를 들어 외부 라이브러리의 경우 메서드를 추가하는 것은 매우 까다롭고 어렵습니다.
또, Java와 달리 Kotlin은 클래스가 기본적으로 final이라서 open 키워드를 달아주지 않는 이상 상속을 받을 수 없습니다.
그래서 Kotlin에서는 Extension. 즉, 확장을 지원합니다.
지금부터 확장에 대해서 알아봅시다.
3. 확장(Extension) 함수
코틀린은 클래스에 상속하거나 디자인 패턴을 사용하지 않고 새로운 기능으로 클래스를 확장 할 수 있는 기능을 제공하는데 이것이 확장(extension)이라는 선언을 통해 이루어집니다.
이때 추가적인 메소드를 구현하면 이를 "확장 함수"라고 하고 추가적인 프로퍼티를 구현하면 "확장 프로퍼티"라고 한다.
기본 구조는, 아래와 같이 원하는 함수명 앞에 확장할 클래스명을 붙여주기만 하면 됩니다!
fun 확장할 클래스.함수명: 리턴타입 {
return 리턴값
}
저는 예제로 Int 클래스를 확장하여, 자동차 주행거리(int타입)값을 천 단위 콤마와 "km"를 붙여 String 타입의 주행거리 포맷으로 변환하는 메소드를 확장 해보도록 하겠습니다.
원하는 함수명(convertToMileage) 앞에, 확장할 클래스(Int)를 붙여주었고, 함수내부에서는 this로 수신 객체 멤버에 접근하였습니다.
수신 객체 타입은 확장이 정의될 클래스의 타입이며, 수신 객체는 그 클래스에 속한 인스턴스 객체 입니다.
특정 클래스에 확장 함수를 추가하려면, 위의 예제처럼
확장할 클래스(수신 객체 타입) 뒤에 .(Dot) 을 찍고, 원하는 메소드명을 정의한 뒤, 함수 내부에서는 this 키워드로 수신 객체 멤버를 사용하여 구현하게 됩니다.
그렇다면, 위에서 생성한 확장함수가 잘 동작하는지 테스트 해보도록 하겠습니다.
참고로, 저는 확장함수들만 따로 관리하기 위해 Extensions.kt 파일을 만들었고, 어디서든 사용할수있도록 클래스밖에 즉, class 파일이 아닌 코틀린(kt) 파일안에 확장함수를 구현해두었습니다.
>>> val mileage = 52000 //int형 변수
>>> println(mileage.convertToMileage()) //확장함수 호출
52,000km
자동차 주행거리값에 해당하는 int형 변수에 마치 Int 클래스의 메소드인것처럼 우리가 위에서 만들었던 확장 함수를 호출할수가 있게 되었습니다!
클래스에 멤버를 새롭게 추가하거나, 기존 함수를 수정하지 않고도 우리의 요구사항대로 함수를 쉽게 추가 할 수 있게 되었네요!
이것이 바로, 확장 함수(Extension Function) 입니다!
단, 확장함수는 수신객체의 private이나 protected 멤버를 사용할수 없기 때문에, 해당 수신객체의 public 멤버에만 접근이 가능합니다.
(확장함수는 외부에서 해당 object에 접근하는 형태이므로, public만 가능한 이유!)
즉, 확장 함수는 캡슐화를 깨지 않는다는 것입니다!
4. 응용
4-1. 클래스 확장
안드로이드 View 클래스를 통해서 visibility를 조작한다고 하면 여러 클래스들의 visibility를 코드를 재사용하며 조작할 수 있습니다.
fun View.hide() {
visibility = View.GONE
}
fun View.show() {
visibility = View.VISIBLE
}
fun View.isVisible(): Boolean {
return visibility == View.VISIBLE
}
String class에 대한 확장함수도 사용해보겠습니다.
email validation 체크함수와 스트링형의 날짜를 Date형으로 바꾸는 예제입니다.
이번 프로젝트에서도 게시판을 만들다보니 날짜 데이터형식을 지정할 일이 많았는데, 날짜형식을 지정하는 함수들을 사용할 때 편리할 것 같습니다.
fun String.isEmailValid(): Boolean {
val pattern = "[a-zA-Z0-9._-]+@[a-z]+\\.+[a-z]+"
return matches(pattern.toRegex())
}
fun String.toDate(format: String): Date? {
val sdf = SimpleDateFormat(format, Locale.getDefault())
return try {
sdf.parse(this)
} catch (e: ParseException) {
null
}
}
SharedPreferences 클래스에 대한 확장 함수로는 값을 저장하고 불러오는 확장함수를 작성해보겠습니다.
fun SharedPreferences.putString(key: String, value: String) {
edit().putString(key, value).apply()
}
fun SharedPreferences.getString(key: String, defaultValue: String = ""): String {
return getString(key, defaultValue) ?: defaultValue
}
흔히 사용하는 토스트 함수도 확장함수로 구현하여 사용할 수 있습니다.
fun AppCompatActivity.showToast(text: String) {
Toast.makeText(this, text, Toast.LENGTH_SHORT).show()
}
4- 2. Nullable Receiver
nullable타입에 대하여 null처리를 할 수 있습니다.
fun String?.isNullOrBlank(): Boolean {
return this == null || this.isBlank()
}
4-3. 외부 라이브러리 확장
외부 라이브러리에 대해 확장함수를 사용하여 기존 라이브러리에 쉽게 기능을 추가할 수 있습니다.
예를 들어 레트로핏 빌더클래스에 인터셉터를 추가하는 확장함수를 구현한다고 해보면,
fun Retrofit.Builder.addCustomInterceptor() {
val interceptor = Interceptor { chain ->
// 인터셉터 동작을 정의합니다.
val request = chain.request()
.newBuilder()
.addHeader("Authorization", "minju TOKEN")
.build()
chain.proceed(request)
}
this.client(OkHttpClient.Builder().addInterceptor(interceptor).build())
}
레트로핏 빌더에 확장함수로 요청 헤더에 토큰을 추가하는 인터셉터를 정의합니다.
*인터셉터: 네트워크 요청 및 응답을 가로채고 조작함
레트로핏 인스턴스를 생성할 때는 위에서 작성한 인터셉터를 추가하여 생성합니다.
val retrofit = Retrofit.Builder()
.baseUrl("base url 주소")
.addCustomInterceptor()
.build()
5. 퀴즈
5-1. 확장 함수는 오버라이딩이 가능할까요?
open class Idol {
open fun sing() {
println("노래 노래~")
}
}
fun Idol.dance() {
println("댄스 댄스~")
}
class BTS: Idol() {
// 오버라이딩 가능
override fun sing() {
}
// 오버라이딩 불가능
override fun dance() {
}
}
이전 목차에서 계속 언급했지만 확장 함수는 클래스의 일부가 아닙니다.
그렇기 때문에 확장 함수는 오버라이딩 할 수 없습니다!
정답은 X !
5-2. 확장 함수는 오버로드가 가능할까요?
class Me {
fun getHeight(height: Int) {
println("제 키는 ${height}cm 입니다.")
}
}
fun Me.getHeigth(height: Float) {
println("제 키는 ${height}cm 입니다.")
}
fun main() {
val me = Me()
me.getHeight(180) // 제 키는 180cm 입니다.
me.getHeigth(180.3F) // 제 키는 180.3cm 입니다.
}
클래스가 가지고 있는 메서드와 확장 함수의 이름이 같더라도
매개 변수 타입이 다르면 오버로드가 됩니다.
즉, 확장 함수는 오버로드가 가능합니다.
정답은 O !
5-3. 멤버 메서드 이름과 확장 함수 이름이 같으면? 확장 함수가 실행될까요?
class Me {
fun getHeight(height: Int) {
println("제 키는 ${height}cm 입니다.") // 이게 실행됨 (우선시 됨)
}
}
fun Me.getHeigth(height: Int) {
println("${height}cm 이 되고 싶어요...")
}
fun main() {
val me = Me()
me.getHeight(180) // 제 키는 180cm 입니다.
}
함수명과 메서드명까지 같아버린다면
에러는 나지 않지만 클래스가 가지고 있는 메서드가 우선시 됩니다.
정답은 X !
5-4. 확장 함수는 프로젝트 안의 모든 곳에서 사용할 수 있을까요?
import 확장함수가 위치한 경로
// ex) import com.example.presentation.views.getHeigth
fun main() {
// 확장함수 사용
}
확장 함수를 정의했어도 프로젝트의 모든 곳에서 사용할 수 있는 것은 아닙니다.
import를 하면 사용 가능합니다. (확장 프로퍼티도 마찬가지!)
정답은 X !
5-5. 확장 함수는 정적 바인딩 된다?
open class Idol {
open fun sing() = println("아이돌이 노래를 불러요")
}
class BTS: Idol() {
override fun sing() = println("BTS가 노래를 불러요")
}
Idol을 상속받는 BTS 클래스를 만들어봤습니다.
sing 메서드를 오버라이딩 하고 있습니다.
open class Idol {
open fun sing() = println("아이돌이 노래를 불러요")
}
class BTS: Idol() {
override fun sing() = println("BTS가 노래를 불러요")
}
fun Idol.dance() = println("아이돌이 춤을 춰요")
fun BTS.dance() = println("BTS가 춤을 춰요")
그리고 Idol의 확장 함수 dance와
BTS의 확장함수 dance를 만들었습니다.
open class Idol {
open fun sing() = println("아이돌이 노래를 불러요")
}
class BTS: Idol() {
override fun sing() = println("BTS가 노래를 불러요")
}
fun Idol.dance() = println("아이돌이 춤을 춰요")
fun BTS.dance() = println("BTS가 춤을 춰요")
fun main() {
// 부모 타입으로 자식 객체 생성
val bts: Idol = BTS()
bts.sing() // --------- 1번
bts.dance() // -------- 2번
}
부모 타입으로 자식 객체를 생성한 다음
sing(일반 함수)과 dance(확장 함수)를 호출했을 때 각각 어떤 결과가 나올까요?
1번과 2번 실행문의 결과를 예측해봅시다.
open class Idol {
open fun sing() = println("아이돌이 노래를 불러요")
}
class BTS: Idol() {
override fun sing() = println("BTS가 노래를 불러요")
}
fun Idol.dance() = println("아이돌이 춤을 춰요")
fun BTS.dance() = println("BTS가 춤을 춰요")
fun main() {
// 부모 타입으로 자식 객체 생성
val bts: Idol = BTS()
bts.sing() // BTS가 노래를 불러요
bts.dance() // 아이돌이 춤을 춰요
}
결과는 위와 같이 나옵니다.
그 이유는 확장 함수가 정적 바인딩되기 때문입니다.
일반 함수를 호출할 때는 객체를 따라가지만
(위 예제에서는 BTS 객체니까 BTS의 sing을 호출합니다)
확장 함수는 타입을 따라갑니다.
(위 예제에서는 Idol 타입이므로 Idol의 dance를 호출함)
일반 함수는 동적 바인딩 되어서 -> 런타임 시간에 함수의 메모리 주소가 결정되는 거고
확장 함수는 정적 바인딩 되어서 -> 컴파일 시간에 Idol의 메모리 주소로 결정되어 버립니다.
확장 함수는 정적 바인딩이 됩니다.
정답은 O !
참고자료
https://todaycode.tistory.com/176
Kotlin 확장 함수(Extension Function)를 아시나요?
1. 요약 2. Extension 3. Extension Function 3-1. 개념 3-2. 예제 4. Extension Property 4-1. 개념 4-2. 예제 5. 퀴즈 5-1. 확장 함수는 오버라이딩이 가능할까? 5-2. 확장 함수는 오버로드가 가능할까? 5-3. 멤버 메서드
todaycode.tistory.com
https://0391kjy.tistory.com/18
코틀린(Kotlin) - 확장 함수 만들기
자바 컬렉션에는 디폴트 toString()이 구현되어 있습니다. 하지만, 그 디폴트 toString()의 출력 형식은 고정되어 있기 때문에, 우리에게 필요한 형식이 아닐 수도 있습니다. val list = listOf(1, 2, 3) >>> pri
0391kjy.tistory.com
[Kotlin] 확장함수를 활용하는 법(how to use Extension Function)
안녕하세요 쭈피셜입니다🖐️9기 1학기가 종료되었습니다🥲🥲 벌써 싸피에 입과한지 5개월이 지났다니 시간은 정말 빠릅니다..프로젝트하느라 어떻게 지나갔는지 모를 5월이었는데요!짧은
velog.io
https://interlude.tistory.com/17
[Kotlin] 확장 함수! 신기한 당신의 정체는?
당신이 코틀린을 사용하고 있다면, '확장 함수'를 사용해봤거나 들어봤을 것이다. (아니어도 상관없다. 지금부터 알아보자) 코틀린은 확장 함수라는 기능을 제공한다. 우선 확장 함수가 어떻게
interlude.tistory.com
https://velog.io/@ho-taek/Kotlin-%ED%99%95%EC%9E%A5-%ED%95%A8%EC%88%98Extension-Function
[Kotlin] 확장 함수(Extension Function)
2021- 06 - 24 2021년도 1학기 IT창업 동아리 SOPT에 7차 세미나때 배웠던 내용을 정리하고 추가하기 위해서 작성하였다.
velog.io
'Kotlin' 카테고리의 다른 글
[Kotlin] 불변성(Immutable)과 가변성(Mutable) (5) | 2024.11.07 |
---|---|
[Kotlin] 코틀린에서의 Null 처리 방법 (4) | 2024.11.05 |
[Kotlin] 컬렉션(Collection) 함수 (9) | 2024.11.04 |
[Kotlin] by란? (0) | 2024.11.02 |
[Kotlin] open class와 abstract class (0) | 2024.11.02 |