Strong 감자의 공부

Ch_05 클래스 알아보기 본문

문법_Kotlin

Ch_05 클래스 알아보기

ugyeong 2023. 8. 11. 15:16

-목차

1. 클래스 알아보기

2. 상속

3. 다양한 클래스

4. object

5. 확장

 


1. 클래스 알아보기

🔶1.1 클래스 정의 개괄

 

⚫지시자 정의 

▪️ 상속 지시자

- open : 상속할 수 있는 클래스 앞에 지정

- final : 코틀린은 상속을 못하는 클래스가 기본이고 표시하지 않으면 상속할 수 없는 클래스.

 

▪️ 다른곳에서 클래스에 접근가능한지 나타내는 visuality modifier

public, internal, protected, private 

 

⚫클래스 머리부 정의 

▪️ 정의 키워드 : class

▪️ 클래스 이름은 파스칼케이스(예)MainActivity.kt 처럼 쓴다.

▪️ 주 생성자(primary constructor) 

<특징>

- 생략가능

- 접근지시자 private을 사용할 경우 주 생성자constructor 생략불가

 

▪️ 클래스/ 인터페이스 상속(표기가 : )

클래스는 하나만 상속가능, 인터페이스는 여러개 상속가능 

(클래스를 하나만 상속가능하게 한 이유는 만약 클래스다중상속을 허용할 시  예를 들어 A ,B 클래스 둘다 C클래스를 상속받는데, D클래스가 A, B 클래스를 상속받는다면, A, B클래스에서 각각 오버로딩힌 부분에서 문제가 생긴다. 그래서 클래스 다중상속이 아닌 인터페이스 다중 상속이 있는 것 같다.)

 

⚫클래스 몸체부 정의

▪️ 초기화 블록(init) : 주 생성자는 머리부에 정의 돼 있어 내부로직을 넣을 수 없으므로 init이라는 초기화 블록을 제공. 

주 생성자가 호출될 때 초기화 블록 내부의 코드가 실행 

init블록에서는 해당 블록보다 위에 선언된 멤버 변수 그리고 생성자 변수만 사용할 수 있다.

 

▪️ 보조 생성자(secondary constructor)

- 여러개 생성가능 

- 본문에 constructor 이름으로 정의

- 주 생성자와 같이 정의된 경우 두번째 보조 생성자는 반드시 주 생성자를 this로 위임 호출해야함. (예) :this() 

- 보조 생성자가 두 개 이상 정의될 경우 다른 보조생성자를 this로 호출 가능. (예) :this() 

-보조 생성자를 정의할 때 주 생성자처럼 매개변수를 속성(var/ val)으로 지정 불가.

 

▪️ 멤버변수(속성)

- 주생성자만이 속성( 매개변수에 val, var붙인 것 )을 선언할 수 있다.

 

▪️ 멤버 함수(메서드)

 

▪️ object (내부클래스정의)

- object / companion object

 

⚫클래스 참조와 클래스 정보 조회

fun main(){
    class Animal()
    class Klass{}

    val a= Animal() // 인스턴스 생성
    val k =Klass()

    println(a::class) // 클래스 참조
    println(k.javaClass.kotlin) // 클래스 정보 확인
}
// 결과값 
class ExpKt$main$Animal
class ExpKt$main$Klass

 

🔶1.2 생성자로 객체 만들기 

생성자를 쓰는 이유는 각 객체를 다르게 관리할 수 있기때문이다. (생성자 없이 작성한 코드는 후에 나온다. )

 

⚫주 생성자로 객체 초기화 및 생성

- 주 생성자를 사용할때 1. this로 초기화를 해주거나 2. var/val를 매개변수에 써야한다. 

fun main() {
    class Person(name: String, age: Int) { //..1
        val name = name
        val age = age
    }

    class Person(val name: String, var age: Int) // ..2

    class Person(name: String, age: Int) // ..3 초기화 안됨 

    val p = Person("ddd", 23)
    println(p.name + p.age)
}

 

💛그런데 주생성자에는 매개변수 혹은 속성(val/ var +매개변수)만 정의할 수 있다. 그래서 주생성자의 추가적인 로직을 처리하기위해 init을 사용한다. 

⚫ init 사용해서 초기화 블록 실행 

-클래스의 주 생성자에게 매개변수만 받고 주생성자가 호출된 다음에 호출된다. 

fun main() {
    class YesConstructor(name: String, age: Int) {
        var name: String = ""
        var age: Int = 34

        init {
            this.name = name // 위에 정의 된 name ""을 무력화시킴
            this.age = age
        }

    }

    val p = YesConstructor("감자", 25)
    println(p.name + p.age)

}
//결과값
감자25

아래는 주 생성자나 보조 생성자가 없이 클래스 정의를 하였다. 하지만 이렇게 작성하면 아래 코드에서 보이듯이 각 객체의 상태를 다르게 관리할 수가 없다!!  

-> 생성자를 작성하면 각 객체를 다르게 관리할 수 있다.

fun main() {
    class NoConstructor {
        val name: String = ""
        val age: Int = 34

        init {
            println("초기화 처리")
        }
    }
    val p = NoConstructor()
    println(p.name + p.age)

    val a = NoConstructor()
    println(a.name + a.age)
}
// 결과값
초기화 처리
34
초기화 처리
34

 

 주생성자 없이 보조생성자로만 작성할수도 있고

주생성자 보조생성자 모두 정의할 수 있는데 보조 생성자가 두개 이상일경우 이때 주생성자가 있다면

앞서 말했듯 두번째 보조생성자부턴 주생성자를 위임호출( : )해야한다. -> this

주생성자가 없다면 당연히 주생성자를 위임호출 할 필요가없다. 

 

⚫ 주생성자 보조생성자 모두 정의

- 보조생성자로 객체를 생성할 때 주생성자도 같이 호출이 된다. 이때 init도 같이 호출이 되어 하단 코드에서 1번이 호출된 후 2번이 호출된다.

fun main() {
    class Person(name: String) {
        var name: String = ""
        var age: Int = 3

        init {
            this.name = name
            this.age = age // 1번 : 주생성자에는 age 매개변수가 없기 때문에 init위 변수 age로 초기화
            println("init 호출")
        }

        constructor(age: Int, name: String) : this(name) {
            this.age = age // 2번 : 1번에서 초기화가 되었지만 여기서 다시 초기화가 됨.
        }

    }

    val p = Person(5, "감자") // 보조생성자 이용해서 객체 생성
    println(p.name + p.age)

}
// 결과값
init 호출
감자5

- 주 생성자로 객체를 생성할 경우 age는 정의해둔 속성(3)이 들어간다.

fun main() {
    class Person(name: String) {
        var name: String = ""
        var age: Int = 3

        init {
            this.name = name
            this.age = age // 1번 : 주생성자에는 age 매개변수가 없기 때문에 init위 변수 age로 초기화
            println("init 호출")
        }

        constructor(age: Int, name: String) : this(name) {
            this.age = age // 2번 : 1번에서 초기화가 되었지만 여기서 다시 초기화가 됨.
        }

    }

    val p1 = Person(5, "감자") // 보조생성자 호출해서 객체 생성
    println(p1.name + p1.age)
    val p2 = Person("감자") // 주생성자 호출해서 객체 생성
    println(p2.name + p2.age)
}
// 결과값
init 호출
감자5
init 호출
감자3

 

💛보조생성자는 왜 사용할까? 위 코드에서 보듯이 생성자들마다 가지고 있는 속성들이 다르면, 객체를 생성할 때 인수(argument)들의 개수, 위치인자 혹은 이름 인자에  따라  다양하게 객체를 초기화할 수 있기 때문이라 생각한다.

클래스안에 속성들의 기본값을 정의해두고 보조생성자들은 각기 다르게 속성들을 초기화 해주면 다양한 값을 갖는 객체들을 생성할 수 있다.

 

❤️ 초기값을 사용할 필요가 없으면 주 생성자에 val/var를 통해 매개변수를 정의하고 
초기값을 사용할 필요가 있다면 초기값을 블럭 안에 정의 후 init에서 this를 이용해 초기화 해준다.

 

🔶1.3  visubility modifier(가시성 변경자)

 

패키지 내부에서 visubility modifier(가시성 변경자)

- public : 어디서나 사용 가능. 공개가 기본이라 지정하지 않으면 공개지시자

- internal : 프로젝트 내의 컴파일 단위의 모듈만 허용. 모듈의 단위는 maven, Gradle등에서 지정한 범위에 따름. 

- private : 자기 클래스내부에서만 사용 가능 

 

 클래스 내부에서 visubility modifier(가시성 변경자) + 상속

- public : 어디서나 사용 가능. 공개가 기본이라 지정하지 않으면 공개지시자

- internal : 프로젝트 내의 컴파일 단위의 모듈만 허용. 모듈의 단위는 maven, Gradle등에서 지정한 범위에 따름. 

- protected : 자기클래스나 상속받는 클래스에서만 허용 

- private : 자기 클래스내부에서만 사용 가능 

 

오버로딩할 변수에 open 예약어 붙이기 

fun main() {
    val s = Sub()
    val c = Compo(s)
    c.display()
}

open class Super {
    private val a = 1
    protected open val b = 2 // 오버로딩 + 상속 가시성 속성
    internal open val c = 3 // 오버로딩 + 모듈가시성 속성
    val d = 4

    protected class Nested {
        public val e: Int = 5
    }
}

class Sub : Super() {
    override val b = 5
    override val c = 7
}

class Compo(val o: Super) { // super 클래스 상속받는 혹은 Super 클래스가 매개변수
    fun display() {
        println(o.c)
        println(o.d)
    }
}
// 결과값
7
4

주생성자 visubility modifier(가시성 변경자)

주생성자에 private을 지정하면 객체를 생성할 수 없지만 내부맴버는 private멤버를 참조할 수 있어 동반객체내에 객체를 생성하는 메서드를 정의할 수 있다. -> 🔶 4.3 동반객체에서 다룸

 

2. 상속

상위 클래스에 open과 override가 되어있는 메서드. 속성만 하위클래스에서 재정의 가능하다.

 

🔶 2.1 상속관계

아래 예제에서 toString()은 객체를 호출하면 자동호출된다.

자바에서 String str="감자" sout(str) 하면 감자라고 자동 호출되는것과 같은 맥락이다.

fun main() {

    val sup = Super("name")
    println(sup)
    val sub = Sub("gamja")
    println(sub)
}

open class Super {
    var name = "dd"
    override fun toString(): String = "super id = ${this.hashCode()}"

    constructor(name: String) {
        this.name = name
    }
}

class Sub(name: String) : Super(name) {
    override fun toString(): String {
        return "sub id = ${this.hashCode()}"
    }
}
// 결과값
super id = 668386784
sub id = 363771819

 

상속관계에 따른 제약

- 서브 클래스에서 서브클래스객체를 이용해  final된 부모 메서드에 접근시, 그 메서드가 부모 클래스에서 override 된것이라도 바로 현재 서브클래스의 윗단계인 부모에서 재정의 된 메서드가 호출 된다.

 

 상속에 따른 생성자 호출

- 생성자 호출 순서가 부모클래스 생성자 -> 자식 클래스 주 생성자->자식클래스 보조 생성자 순이다.

 

⚫ 슈퍼 클래스와 서브클래스에 보조 생성자만 구성된 경우

자기클래스에서는 this로 생선자의 위임 호출 진행

슈퍼클래스의 위임호출은 보조 생성자에서 직접 슈퍼클래스의 보조 생성자를 호출 해야한다. ->:super()

fun main() {
    println("생성자 호출 순서 1 : ")
    val s1 = Student("감자", 25)
    println("생성자 호출 순서 2 : ")
    val s2 = Student("돌자반", "남성", 27)
}

// 부모 클래스
open class Person {
    val name: String
    var gender: String = "여성"

    constructor(name: String) {
        this.name = name
        println("슈퍼 클래스 보조 생성자 1 실행 ")
    }

    constructor(name: String, gender: String) : this(name) {
        this.gender = gender
        println("슈퍼클래스 보조 생성자 2 실행")
    }

}
// 자식 클래스
class Student : Person {
    var age: Int = 0

    constructor(name: String, age: Int) : super(name) {
        this.age = age
        println("서브클래스 보조 생성자 1 고고")
    }

    constructor(name: String, gender: String, age: Int) : super(name, gender) {
        this.age = age
        println("서브클래스 보조 생성자 2 고고")
    }


}
// 결과값
생성자 호출 순서 1 :
슈퍼 클래스 보조 생성자 1 실행
서브클래스 보조 생성자 1 고고
생성자 호출 순서 2 :
슈퍼 클래스 보조 생성자 1 실행
슈퍼클래스 보조 생성자 2 실행
서브클래스 보조 생성자 2 고고

 

3. 다양한 클래스

🔶3.1 내포 클래스(Nested Class), 이너 클래스(Inner Class), 지역 클래스 (Local Class)

 

클래스를 정의 하는 곳 

-  패키지 단위 : 일반적인 클래스 정의

-  클래스 단위 : 내포 클래스(Nested Class), 이너 클래스(Inner Class) 정의 <- 자바랑 내포와 이너 개념이 반대이다!!!

이때 내포 클래스(Nested Class)는 실제 아웃 클래스랑 밀접한 관계가 없다.

-  함수단위 : 지역 클래스 (Local Class)는 함수 안에 정의한다.

함수내부는 외부에서 접근할 수 없는 지역 영역이라 클래스도 지역역영에서만 사용할 수 있다. 그래서 특별한 가시성 없이도 지역에서만 사용가능하다.

 

 

⚫ 내포 클래스(Nested Class)

우리가 아는 클래스모양을 특정 클래스 내부에 정의하는 것을 내포클래스(Nested Class)라 한다. 이건 내포클래스를 감싼 밖 클래스랑 연관없다. = 내포클래스는 자신을 감싼 외부클래스 내의 속성등에 직접 접근 불가

내포 클래스를 외부에서 사용할 때는 외부(감싼)클래스 이름으로 접근해서 사용한다. 내포 클래스의 객체를 생성할 때는 외부 클래스 이름으로 접근해서 내포 클래스 생성자를 호출해 객체를 생성

fun main() {
    val demo = Student.Nested()
    println(demo.foo1()) // private 멤버 참조
    
}

class Student {
    var age: Int = 0 // 외부 클래스 속성

    class Nested {
        private val nestVar = 100
        fun foo1() = nestVar
        fun foo2() =this@Student // 외부 클래스 지칭불가
        fun foo3() =this@Nested // 자기 자신 가능
    }
}
// 결과값
100

 

 이너 클래스(Inner Class)

이너 클래스는 외부 클래스의 멤버처럼 사용되므로 외부 클래스의 비공개 속성도 사용 가능 

외부클래스의 객체는 this@외부클래스이름 으로 사용한다. 

외부클래스-> 이너클래스 사용할려면 객체 생성 후 사용 

fun main() {
    val demo = Student().pencil().foo1()
    println(demo)
    Student().getBar()
}

class Student {
    private var age: Int = 0 // 외부 클래스 속성

    // 이너클래스
    inner class pencil {
        private val bar = 100
        fun foo1() = this@Student.age // 외부 클래스 private 속성 접근 
        fun foo2() = bar 
    }
    fun getBar() = println(pencil().foo2()) // 외부클래스에서 이너 클래스를 객체로 접근
}
// 결과값
0
100

 

 지역 클래스(Local Class)

함수기능이 복잡해지면 함수안에 클래스를 정의하는 것이다. 지역클래스도 상속관계를 처리 할 수 있다.

 

아래는 팩토리함수이다.

팩토리 함수는 객체를 반환값으로 하여 로컬클래스를 외부에서 쓸 수 있게 해준다.

팩토리 함수의 반환값 : Amp인터페이스 -> 로컬클래스 객체 반환 받을 수 있다. 

로컬 클래스 상속: Amp인터페이스

fun main() {
    val amp = createAmp() // 내부클래스 객체 생성
    amp.foo() // 내부 클래스 메서드 실행
    println(amp.javaClass.kotlin)
}

interface Amp {
    fun foo(): Unit
}

fun createAmp(): Amp { // 인터페이스로 반환

    // 로컬 클래스
    class Pencil : Amp {

        override fun foo() = println("로컬클래스 객체 반환") // 인터페이스 메소드 재정의

    }
    return Pencil()
}
// 결과값
로컬클래스 객체 반환
class ExpKt$createAmp$Pencil

 

메서드(클래스 블럭에서 정의한 함수 )에서 전역변수 참조와 전역변수 갱신 가능하다. 

마찬가지로 이너클래스의 메서드에서도 전역변수 참조가 가능하다.

 

🔶3.3 이너클래스의 상속관계

 

⚫이너클래스에서 this와 super표기 

▪️ this : 이너클래스의 객체는 항상 this로 참조

▪️ this@외부클래스 : 이너클래스에서 외부클래스의 속성은  this@외부 클래스 이름

▪️ super@ 외부클래스 : 외부 클래스의 상속관계는 this가 아니라 super  씀.

 

4. 싱글턴 패턴 object

<종류>

1. object표현식: 익명 클래스로 특정 클래스없이 객체  생성

2. object 선언으로 하나의 객체만 만들어서 사용

3, companion object : 클래스에 정의해 클래스와 같이 동반해서 사용 

 

🔶 4.1 익명객체인 object

 ⚫ 쓸 수있는곳

1. 최상위 변수2. 함수내부 또는 메서드 내부에서 지역변수에 할당3. 함수의 매개변수4. 내부함수의 반환값으로 정의

=> 일회성 객체가 필요한 경우에 내부에 정의해 바로 사용

 

⚫ 함수의 매개변수에 익명 객체 전달

함수의 인자를 전달할 때 익명객체를 바로 정의해서 전달한다. 

fun main() {
    val a = getObject(object : Personnel { // 인터페이스를 상속받는 익명객체
        override val name = "감자"
        override val age: Int = 25
    })

    println("객체반환이름 : ${a.name} 나이 =${a.age}")
}

interface Personnel {
    val name: String
    val age: Int
}

// 객체 반환함수
fun getObject(p: Personnel): Personnel { // 매개변수, 반환값이 인터페이스
    return p
}

// 결과값
객체반환이름 : 감자 나이 =25

 

⚫ 클래스와 인터페이스를 상속하는 object 표현식 처리

부모 클래스 A(10)을 상속하여 super.y를 이용해 10을 y값으로한다.

open class A(x: Int) {
    open val y: Int = x
    open fun display() = y
}

interface Add {
    fun add(x: Int, y: Int): Int
}

fun main() {
    // 반환값이 인터페이스
    val b: A = object : A(10), Add {
        override val y = super.y // A(10)값
        override fun add(x: Int, z: Int) = x + z
        override fun display() = y + add(55, 77)
    }
    println("A클래스 y값"+b.y)
    print("오버라이드한 display 호출"+b.display())
}

// 결과값
A클래스 y값10
오버라이드한 display 호출142

 

🔶 4.2 싱글턴패턴 object : 하나의 객체만 만들어서 사용

- 주생성자만 없을뿐 클래스 정의와 같다.( 내부에 객체, 클래스 정의도 가능) 

- 블럭 안 const val 가능 

-별도의 객체를 생성하지 않고 object 이름으로 직접 내부속성과 메서드에 접근

- 상속 받는거 가능 

- object 정의를 처음으로 사용할 때 메모리에 로딩됨 

 - 클래스 내부에  object 정의 가능

 

⚫ 클래스 내부에 object 정의 사용

내부 object 정의는 외부 클래스와 아무 상관이 없어 이 객체를 부를 때 클래스의 이름으로 접근 후 object이름에 접근하여 메서드를 실행한다. 

class Person(val name: String, val age: Int) {
    object inner {
        fun boo() = "bar"
        fun getPerson(p: Person) = p.info()
        fun create(name: String, age: Int) = Person(name, age) // 외부클래스의 객체 생성

    }

    fun info() = "이름 $name, 나이= $age"
}

fun main() {
    val p = Person.inner.create("감자", 25)
    println(p.info())
    println(Person.inner.getPerson(p))
}
//결과값
이름 감자, 나이= 25
이름 감자, 나이= 25

 

🔶 4.3 companion object : 클래스와 상호작용하는 동반객체

 

동반객체는 클래스와 하나처럼 움직이도록 구성되었다. 그래서 클래스이름.메서드(동반객체 안에 정의된)이름으로 호출이 가능하다. 클래스 내부의 동반객체는 하나만 만들어진다. 그래서 여러객체에서 동번객체를 공유해서는 안된다. 

 

⚫ 메서드를 이용한 생성자 패턴 - 주 생성자가 private일 때 동반객체를 이용해 객체 생성하기

class Person private constructor(val name: String) {
    var age: Int = 0 // var로 해야함!
    companion object {
        fun create(name: String, age: Int) : Person {
            val result = Person(name)
            result.age = age
            return result
        }
    }
}

fun main() {
    val p = Person.create("감자", 25)
    println("이름 : ${p.name}" + "나이 : ${p.age}")
}
//결과값
이름 : 감자나이 : 25

 

⚫ 내부 동반객체 안 private 변수는 메서드를 통해서만 참조할 수 있다.

 

⚫ 내포클래스에서 외부 클래스의 동반객체를 참조

fun main() {
    val demo = Student.Nested()
    println(Student().age) // private 멤버 참조

}

class Student {
    var age: Int = 0 // 외부 클래스 속성

    class Nested {
        private val nestVar = "$com"
        val a = Student().age
        val b = com
    }

    companion object {
        const val com = " 동반객체 상수"
    }
}

 

5. 확장

클래스나 함수, 동반객체, object를 직접 수정하지 않고 기능을 추가하는 방법을 확장이라고 함.

주의)

1. 내부적으로 backing Field사용 불가

2. 확장은 비공개 속성이나 메서드는 접근이 불가하다. 접근할려면 클래스내에 별도의 메서드를 정의해서 비공개속성이나 메서드를 호출해줘야한다.

3. 클래스멤버가 아니므로 확장을 선언한 패키지나 클래스도 함께 import해야 사용가능하다.

 

Bakcing Field란?

Kotlin의 Bakcing Field는 getter/setter에 기본적으로 생성되는 속성(property)이다. Backing Field는 accessor(getter/setter)안에서 field 키워드를 사용하여 접근할 수 있다. 확장속성, 인터페이스는 Bakcing Field가 없기때문에 getter/setter를 직접 정의해야한다.

 

⚫ 최상위 속성에서는 private setter가 작동하지 않는다. 

var private: Int = 100
    get() = field * 2
    private set

fun main() {
    println("최상위 속성 :"+private)
    private = 200
    println("최상위 속성 :"+private) // 최상위 변수값 바뀜

    // 클래스 속성
    val a = Student(200)
    println("클래스 속성 :"+a.privateA)
    // a.privateA =100 안됨
    a.setA(300) // 메서드로 접근해 바꿈
    println("클래스 속성 :"+a.privateA)
}

class Student(age: Int) {
    var privateA: Int = age
        private set

    fun setA(value: Int) {
        privateA = value
    }
}
// 결과값
최상위 속성 :200
최상위 속성 :400
클래스 속성 :200
클래스 속성 :300

 

⚫ 일반클래스의 속성 확장하기

주의) 속성과 확장속성의 이름이 같을 경우 속성이 먼저 조회되므로 확장속성은 이용할 수 없다. 확장속성을 사용하려면 기존 속성을 비공개 처리하면 확장속성만 외부에서 사용할 수 있다. 

아래 코드에서 Temp클래스의 속성에 화씨온도 float값을 확장시키는 것인데 이때 Temp를 리시버 클래스라고 한다.

섭씨온도를 화시온도로 변환하는 속성을 확장속성으로 의

게터는 화씨온도를 표시

세터는 섭씨온도를 생신처리하고

class Temp(var 섭씨온도: Float)

val a = Temp(100f)

var Temp.화씨온도: Float
    get() = (섭씨온도 * 9 / 5) + 32
    set(value) {
        섭씨온도 = (value - 32) * 5 / 9
    }

fun main() {
    println("화씨온도 : " + a.화씨온도)
    println("섭씨온도 : " + a.섭씨온도)
    a.화씨온도 = 30f
    println("화씨온도 : " + a.화씨온도)
    println("섭씨온도 : " + a.섭씨온도)

}
// 결과값
화씨온도 : 212.0
섭씨온도 : 100.0
화씨온도 : 30.0
섭씨온도 : -1.1111112

클래스 속성확장 : var / val 클래스명.추가할 속성명 : 자료형

objec속성확장 : var/val 오브젝트명.추가할 속성명 : 자료형

동반객체 속성확장 : var/val 클래스이름.Companion.추가할 속성명 : 자료형

 

🔶 5.2 확장함수

확장속성처럼 메서드도 클래스나 object에 추가할 수 있다.

주의) 확장함수도 외부함수이므로 비공개 속성이나 메서드는 접근이 불가하다. 접근할려면 클래스내에 별도의 메서드를 정의해서 비공개속성이나 메서드를 호출해줘야한다.

 

fun 클래스명.함수명(매개변수): 반환타입 {

구현부 }

// 클래스명에는 확장 함수 호출시 클래스의 객체가 들어간다. 

// 클래스명에 널러블 객체가 들어갈 수도 있다.

⚫ 사용자 클래스에 확장함수 정의

class Person(val first: String, val last: String)

fun main() {
    val p = Person("감", "자")
    fun Person.getName() = this.first + this.last // 확장 함수, 리시버로 객체 받아서 this로 속성 참조 가능 

    println(p.getName())
    println(p::first.name) //속성 참조 후 속성명 조회
    println(p::first.get()) //속성 참조 후 속성 "값" 조회
}
// 결과값
감자
first
감

 

⚫ object 정의에 확장함수 정의

아래 코드에서 to가 쓰였는데 이는 튜플을 만드는 함수다. ( , ) 형식

object Person

fun main() {
    fun Person.swap(one: Int, other: Int): Pair<Int, Int> {
        val (second, first) = one to other // 구조분해, (second, first) = (one, other) =(100, 200)
        return first to second // (first , second) 튜플 반환
    }
    println(Person.swap(100, 200))
}
// 결과값
(200, 100)

// 위치수정 

object Person

fun main() {
    fun Person.swap(one: Int, other: Int): Pair<Int, Int> {
        val (second, first) = one to other // 구조분해, (second, first) = (one, other) =(100, 200)
        return second to first // (second , first) 튜플 반환
    }
    println(Person.swap(100, 200))
}
// 결과값
(100, 200)

 

⚫ 확장함수 내부에 object객체를 만들어서 그 객체를 처리할 때

말로는 어려운거 같다! 아래 코드를 보면 포인트는 this@truc2가있는데 이는 확장함수 안에 리시버 객체와 object표현식이 있다. 이때는 리시버 객체를 참조할 때 this만 사용할 수 없다. 그래서 확장함수의 리시버 객체를 참조하기 위해 this@확장함수명을 사용한다. 

추가로 호출순서도 println으로 찍어봤다.

fun String.truc1(max: Int): String {
    if (length <= max) return this // this는 String리시버 객체 가리킴
    else return this.substring(0, max)
}

interface Act {
    fun action(max: Int): String
}

fun String.truc2(max: Int): String {
    val aaa = object : Act {
        override fun action(max: Int): String {
            println("aaa객체 생성")
            if (length <= max) return this@truc2 // thuis@truc2는 String리시버 객체 가리킴
            else return this@truc2.substring(0, max)
        }
    }
    println("aaa객체 뒤")
    return aaa.action(max)
}

fun main() {
    println("안녕하세요감자".truc1(4))
    println("안녕하세요감자".truc2(5))
}
// 결과값
안녕하세
aaa객체 뒤
aaa객체 생성
안녕하세요

 

⚫ 동반객체에 확장함수 정의

AA클래스가 비공개 주 생성자를 가지고 있다. 하지만 AA클래스 내부의 동반객체가 AA클래스 객체를 생성하는 메서드가있고 이에 대한 접근을 확장함수를 이용했다.

 

확장함수없이 위에서 다뤘던것처럼 하단 코드로 해도 된다.

val aa = AA.create("감자")
class AA private constructor(val name: String) {
    companion object {
        fun create(name: String): AA {
            return AA(name)
        }
    }
}

fun AA.Companion.create2(name: String): AA {
    return this.create(name)
} // this는 AA.Companion

fun main() {
    val aa = AA.create("감자")
    println(aa.name)
}
// 결과 
감자

 

 

🔶 5.3 멤버와 확장 이름 충돌 주의할 사항

확장 속성이나 확장함수는 클래스의 멤버가 아니라 클래스의 멤버와 이름이 충돌 날 경우 항상 클래스 멤버가 우선이다!!! 단, 멤버가 비공개일 경우 확장이 우선 사용된다.

 

⚫ 멤버와 확장 충돌시 처리 기준 : 상속 포함

다음 코드는 상속한 클래스 안 함수명과 확장함수명이 겹쳤을 때인데 이 경우 확장함수가 무시된다.

open class Person(val name: String, var age: Int) {
    open fun say() = "super 정의 hello"
    fun eat() = "super eat"
}

class Student(name: String, age: Int) : Person(name, age) {
    override fun say(): String {
        return "sub 재정의 hello"
    }

}

fun Student.eat() = "sub 확장 hello" // 확장함수
fun main() {
    
    val p: Person = Student("감자", 25)
    println(p.say())
    println(p.eat()) // 혹장함수 무시됨
}
// 결과값
sub 재정의 hello
super eat

 

멤버와 확장 충돌시 처리 기준 :private이 있는 경우 

class Integer(val x: Int) {
    val value: Int = x
        get() {
            println("클래스 멤버 속성 value")
            return field // value
        }

    private fun display() = x.toString() // private 메서드

    fun plus(other: Integer): Integer {
        println("클래스 멤버 메소드 plus")
        return Integer(this.x + other.x)
    }

    override fun toString(): String = "Integer(value = $x)" // 츌력 지정
}

val Integer.value: Int // 확장 속성 무시됨
    get() {
        println("확장 속성 value")
        return 100
    }

fun Integer.display(): String { // private 멤버의 확장함수
    println("private 멤버 확장 함수 display")
    return this.x.toString()
}

fun Integer.plus(other: Integer): Integer { //  private 아닌 멤버의 확장함수리 무시됨
    println("확장 함수 plus")
    return Integer(this.x + other.x)
}

fun main() {
    val a = Integer(100)

    println(a.value)
    println(a.display())
    println(a.plus(Integer(300)))
}
// 결과값
클래스 멤버 속성 value
100
private 멤버 확장 함수 display
100
클래스 멤버 메소드 plus
Integer(value = 400)