Study Record

[Kotlin] object 본문

안드로이드/Kotlin

[Kotlin] object

초코초코초코 2023. 7. 20. 02:20
728x90

😶 Object

새 하위 클래스를 명시적으로 선언하지 않고 일부 클래스를 약간 수정한 객체를 만들어야 할 때가 있다. Kotlin 은 Object 선언과 Object expression 으로 이를 처리한다.

 

 

😶 Object expression

Object expression 은 클래스 선언으로 명시적으로 선언되지 않은 익명 클래스의 개체를 만든다. 이러한 클래스는 일회용으로 객체 선언과 동시에 정의되거나 기존 클래스에서 상속하거나 인터페이스를 구현할 수 있다. 이렇게 정의된 인스턴스는 클래스 이름을 가지지 않으므로 익명 객체라고도 불린다.

 

 

1. 익명 객체 만들기

val helloWorld = object {
    val hello = "Hello"
    val world = "world"
    
    fun getString(): String = "$hello $world"
	
    override fun toString() = "$hello $world"
}

println(helloWorld.getString())
println(helloWorld.toString())

익명 객체는 생성자(constructor)를 가질 수 없다. init 초기화 블록은 실행된다.

 

 

 

2. 상속받은 익명 객체

익명 객체를 생성할 때 어떤 타입(들)이든 상속받을 수 있다. object 객체 뒤에 ":" 을 붙인 뒤 상속받을 타입을 선언한다. 일반적인 클래스 선언과 비교하면 다음과 같다. 

class TestClass() : A() {
    /* class Body */
}

val testObject = object : A() {
    /* Object Body */
}

 

Android 에서 자주 사용하는 setOnClickListener 함수를 선언하는 방법과 비슷한 상황을 예시로 들면 다음과 같다. 한 가지 특징이 있다면, 같은 블록 단위에서 실행된 변수를 Object 에서 사용할 수 있다. 밑의 예시로는 count 변수가 있다.

interface OnTestClickListener {
    fun onClick(v: TestView?)
}

class TestView {
    var onClickListener: OnTestClickListener? = null;
    public fun setOnTestClickListener(l: OnTestClickListener) {
        println("setOnClickListener setting")
        onClickListener = l
    }
    
    fun click() {
        onClickListener?.onClick(null)
    }
}

fun main() {
    val view = TestView()
    var count = 3
    
    view.setOnTestClickListener(object: OnTestClickListener {
        override fun onClick(v: TestView?) {
            count++
            println("onClick Listener play $count")
        }
    })
    
    view.click()
}
/* 출력 결과 */
setOnClickListener setting
onClick Listener play 4

 

 

익명 객체는 여러개의 타입을 상속받을 수 있다.

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

interface B { /*...*/ }

val ab: A = object : A(1), B {
    override val y = 15
}

 

 

3. 함수의 리턴값으로 사용되는 익명 객체

class C {
    private fun getObject() = object {
        val x: String = "x"
    }

    fun printX() {
        println(getObject().x)
    }
}

 

함수의 리턴값으로 익명 객체가 리턴될 때 아무것도 상속받지 않았다면 Any 타입으로 리턴된다. 상속받은 타입이 한개라면 그 타입으로 익명 객체가 리턴되고 여러개면 명시적으로 어떤 타입으로 리턴할지 정의해야 한다.

interface A {
    fun funFromA() {}
}
interface B

class C {
    // The return type is Any; x is not accessible
    fun getObject() = object {
        val x: String = "x"
    }

    // The return type is A; x is not accessible
    fun getObjectA() = object: A {
        override fun funFromA() {}
        val x: String = "x"
    }

    // The return type is B; funFromA() and x are not accessible
    fun getObjectB(): B = object: A, B { // explicit return type is required
        override fun funFromA() {}
        val x: String = "x"
    }
    
    private fun getObjectPrivate() = object {
        val x: String = "X"
    }
    
    // 맴버 변수 접근 가능
    fun playPrintln() {
        println(getObjectPrivate().x)
    }
}

fun main() {
    // 접근 불가능
    C().getObject().x
}

또한, Object 의 맴버 변수(x)도 전부 접근할 수 없다. 단, private 키워드가 붙은 함수의 리턴값으로 얻은 Object 객체는 맴버 변수(x)에 접근 가능하다.

 

 

 

😶 Object Declarations

 

SingleTon 패턴을 사용할 때 Object Declaration 을 사용하면 쉽게 정의할 수 있다. 처음 객체에 접근할 때 Thread-safe 하게 게 선언 초기화된다.  클래스 이름을 타입으로 객체를 따로 선언할 수 없다. 객체에 처음 접근할 때 선언되고 프로젝트 전체에 하나의 객체만 생성되며 접근할 수 있기 때문에 SingleTon 패턴을 고려할 때 유용하게 사용할 수 있다.

object SingleTonObject {
    val hello = "Hello"
    var count = 3
    
    fun printHello() {
        println("$hello $count")
    }
}

fun main() {
    SingleTonObject.count++
    SingleTonObject.printHello()
    
    // 불가능
    val singleTonObject = SingleTonObject()
}

 

 

Data Object

data class 처럼 Data Object 도 선언이 가능하다. data Class 와는 다른 점은 Data Object 는 SingleTon 객체라는 점이다. 따라서 Data Object 는 인스턴스의 복사본을 만들 수 없어 copy() 함수가 없고 데이터 속성이라는 개념이 없기 때문에 componentN() 함수도 없다.

data object MyDataObject {
    val x: Int = 3
}

 

 

Companion Object

object Declaraion 은 싱글톤 객체를 정의하는데 사용된다. 이 개념을 클래스 내에서도 사용할 수 있다. 바로 companion 키워드를 앞에 붙여 선언해주면 된다. 만약 따로 이름(ex. Factory)을 짓지 않는다면 Companion 이름으로 사용된다.

class MyClass {
    companion object Factory {
        fun create(): MyClass = MyClass()
    }
}

class MyClass2 {
    // 이름이 존재하지 않으므로 Companion 이름 강제 부여
    companion object {
        fun create(): MyClass2 = MyClass2()
    }
}

fun main() {
    // 사용할때는 원래의 클래스 이름을 한정자로 사용하여 멤버를 간단히 호출할 수 있다.
    val instance = MyClass.create()
}

 

companion object 를 참조할때는 이름이 존재한다면 이름으로 혹은 원래 클래스 이름을 한정자로 사용하여 맴버를 간단히 호출할 수 있다.

class MyClass1 {
    val hello = "hello"
    companion object Named {
        fun newInstance() = MyClass1()
    }
}

class MyClass2 {
    val word = "word"
    companion object {
        fun newInstance() = MyClass2()
    }
}

fun main() {
    val myClass1 = MyClass.newInstance()
    val myNameClass1 = MyClass1.Named.newInstance()
    val myClass2 = MyClass2.newInstance()
    val myNameClass2 = MyClass2.Companion.newInstance()
}

 

object Declaraion 은 singleTon 패턴처럼 하나의 객체만 생성된다. 클래스에 object Declaraion 을 선언하는 것이 companion object 이기 때문에 companion object 의 멤버들이 static 처럼 보이지만 런타임 시 실제 객체의 인스턴스 맴버일 때가 있다. 이럴 때 @JvmStatic 어노테이션을 사용하면 static 으로 인식한다.

class MyClass1 {
    val hello = "hello"
    companion object Named {
        @JvmStatic fun newInstance() = MyClass1()
    }
}

 

 

😶 Object expression  과 Object Declaration 의 차이점

expression 은 사용되는 즉시 실행되고 최기화되지만 declaration 은 첫 번째로 접근되어 사용되어질 때 초기화된다.

 

 

 

 

Object expressions and declarations | Kotlin

 

kotlinlang.org

 

 

728x90

'안드로이드 > Kotlin' 카테고리의 다른 글

[Kotlin] coroutines 살펴보기  (0) 2023.08.08
[Kotlin] property  (0) 2023.07.21
[Kotlin] 람다식 및 고차함수  (0) 2023.07.19
[Kotlin] vararg 키워드  (0) 2023.07.17
[Kotlin] null safety  (0) 2023.07.11