[Kotlin] 클래스(4)
📚 클래스(4)
📔 확장 함수
📖 확장 함수 정의와 호출 규칙
- 확장 함수 정의: 함수 이름 앞에 리시버 클래스를 접두사로 붙여서 정의
- 확장 함수 호출: 리시버 클래스 대신 리시버 객체를 지정하고 확장 함수를 메서드로 호출해서 실행
📖 내장 클래스의 확장 함수 정의
fun Int.swap(other: Int): Pair<Int, Int> { // Pair 클래스는 2개의 원소를 가진 튜플
var (first, second) = other to this // 튜플 구조 분해로 변수 교환
return first to second // to 메서드: 2개의 원소를 튜플화
}
fun String.swap(other: String): Pair<String, String> {
var (first, second) = other to this
return first to second
}
println((100).swap(200))
println(("1차").swap("2차"))
(200, 100)
(2차, 1차)
📖 최상위 클래스의 확장 함수 정의
최상위 클래스 Any에 확장 함수를 정의하면 이를 상속한 하위 클래스는 모두 이 확장 함수를 사용할 수 있다.
fun Any.dir(): Set<String> {
val a = this.javaClass.kotlin
var ll = mutableListOf<String>() // 빈 가변 리스트 생성
for (i in a.members) { // 클래스 내의 멤버 조회
ll.add(i.name)
}
return ll.toSet() // 동일한 이름 제거
}
val intM = (100).dir()
println(intM.count()) // 집합에 들어온 멤버의 개수
println(("str".dir().count())) // 문자열 내부의 멤버 개수
println((listOf(1, 2, 3).dir().count())) // 리스트 내부의 멤버 개수
class Person
println(Person().dir().count()) // 사용자 정의 내부의 멤버 개수
31
21
34
3
📖 사용자 클래스의 확장 함수 정의
class Person(val firstName: String, val lastName: String)
val p = Person("달", "문")
fun Person.getName() = this.firstName + this.lastName // 두 개의 속성을 조회하는 확장 함수
println(p.getName())
println(p::firstName.name) // 속성 참조 후 속성 값 조회
println(p::firstName.get()) // 속성 참조 후 속성 값 조회
달문
firstName
달
📖 사용자 클래스의 널러블 확장 함수 정의
class Person(val firstName: String, val lastName: String)
fun Person?.getFullName() : String? { // 널러블 자료형에 확장
if (this == null) { return null } // 널 체크 처리
else {
return this.firstName + this.lastName // 널이 아닌 경우에만 속성 반환
}
}
var p = null
println(p.getFullName())
null
📖 object 정의에 확장 함수 추가
object A
fun A.swap(one: Int, other: Int): Pair<Int, Int> { // 두 수 교환 확장 함수
val (second, first) = one to other // 반환은 튜플
return first to second
}
println(A.swap(100, 200))
(200, 100)
📖 companion object의 확장 함수 정의
class AA private constructor(val name: String) { // 접근 불가 생성자 정의
companion object {
fun create(name: String): AA { // companion object에서 객체 생성
return AA(name)
}
}
}
fun AA.Companion.create2(name: String): AA { // 객체 생성 확장 함수
return this.create(name) // 접근 불가 생성자 호출 대신 컴패니언 내의 생성자 메서드 호출
}
val aa = AA.create("dal")
println(aa.name)
val bb = AA.create2("moon")
println(bb.name)
dal
moon
📖 확장 함수 내부에서 객체를 처리할 때 리시버 객체 처리 방식
fun String.truncator(max: Int): String { // 문자열 자르기 확장 함수 작성
if(length <= max) return this // 길이가 작으면 확장 함수의 리시버 객체 처리
else return this.substring(0, max) // 길이가 크면 문자열 자르기
}
println("문자열자르기".truncator(4)) // 문자열 자르기 처리
interface Actable { // 인터페이스 작성
fun action(max: Int): String // 추상 메서드 작성
}
fun String.truncator2(max: Int): String {
val aaa = object: Actable {
override fun action(max: Int): String {
if (length <= max) return this@truncator2
else return this@truncator2.substring(0, max)
}
}
return aaa.action(max)
}
println("문자열자르기".truncator2(5))
문자열자
문자열자르
interface This { // 인터페이스 작성
val truncated: String // 추상 속성 정의
fun getStr(): String // 추상 메서드 정의
}
fun String.truncator(max: Int) = object: This {
override val truncated
get() = if (length <= max) this@truncator
else this@truncator.substring(0, max)
override fun getStr() = this@truncator
}
val trunc = "문자열 처리".truncator(4)
println(trunc.truncated)
println(trunc.getStr())
문자열
문자열 처리
📖 클래스 내부에 확장 함수 정의
class Extension(var name: String, val n: Int) { // 클래스 정의
fun String.product(x: Int) = this.repeat(x) // 문자열 확장 함수 정의
fun query() = name.product(n) // 확장 함수를 랩핑한 메서드 정의
}
val e = Extension("Hello", 3)
println(e.query())
HelloHelloHello
📔 멤버와 확장의 주의 사항
확장 속성이나 확장 함수는 클래스의 멤버가 아니다. 그러므로 클래스의 멤버와 이름이 충돌되는 경우, 항상 멤버가 우선된다. 단, 멤버가 비공개일 경우에만 확장이 우선 사용된다.
📖 멤버와 확장 간 충돌 시 처리 기준 : 상속 관계
확장 함수와 멤버 또는 메서드 이름이 충돌한 경우, 항상 멤버가 우선이다.
open class Person(val name: String, var age: Int) { // 슈퍼 클래스 정의
open fun say() = "super 정의 hello"
fun eat() = "super eat"
}
class Student(name: String, age: Int, val school: String) : Person(name, age) { // 서브 클래스 정의
override fun say() = "sub 재정의 hello" // 메서드 재정의
}
fun Student.eat() = "sub 확장 ehllo" // 확장 함수 정의
val p: Person = Student("학생", 13, "서울 중학교") // Person 클래스 변수
println(p.say()) // 상속관계에 따른 멤버 메서드 처리
println(p.eat()) // 확장 함수가 아닌 Person 클래스 메서드 처리
sub 재정의 hello
super eat
1.2.1 📖 멤버와 확장 간 충돌 시 처리 기준 : 속성과 메서드
클래스의 속성이나 메서드 중에 접근 지정자로 private이 사용된 경우, 외부에 정의된 확장 함수와 이름이 중복이 되더라도 비공개 멤버는 클래스 외부에서 사용할 수 없기 때문에 충돌이 생기지 않는다.
class Integer(val x: Int) {
val value: Int = x // 속성 정의
get() { // 속성 게터 정의
println("클래스 필드")
return field
}
private fun display() = x.toString() // 비공개 메서드 정의
fun plus(other: Integer): Integer {
println("클래스 메서드")
return Integer(this.x + other.x)
}
override fun toString() = "Integer(value= $x)" // 재정의 메서드
}
fun Integer.plus(other: Integer): Integer { // 클래스 내부 멤버와 이름 충돌
println("확장 함수") // 절대 호출되지 않음
return Integer(this.x + other.x)
}
fun Integer.display(): String { // 멤버와 이름이 충돌되나 private 으로 인해 외부에서 호출 가능
println("확장 함수")
return this.x.toString()
}
val Integer.value: Int // 클래스 필드와 이름 충돌
get() { // 확장 필드는 배킹 필드가 없음
println("확장 필드")
return 100
}
val inte = Integer(100) // 객체 생성
println(inte.plus(Integer(300))) // 클래스 메서드 호출
println(inte.value) // 클래스 필드 호출
println(inte.display()) // 확장 함수 호출
클래스 메서드
Integer(value= 400)
클래스 필드
100
확장 함수
100