본문 바로가기

코틀린 Kotlin

kotlin (5) Object 와 Companion Object

안녕하세요 loner 입니다. compose 관련 작성을 열심히 준비하다가 오랜만에 Kotlin 관련글로 돌아왔습니다. 

어제 새벽에 지인 블로그를 살펴보다가 기본적인 지식들을 우연히 보게 되었습니다.

 

https://jcchu.medium.com/ 

 

JCChu – Medium

Read writing from JCChu on Medium. “기본부터”라는 말을 좋아하지만 정작 기본이 없는… 기본을 쌓아나가려고 합니다. 안드로이드 개발자로 성장하기 위해 열심히 책상앞에 앉아 두들겨보겠습니다. E

jcchu.medium.com

 

위 블로그를 보다가 기본적인 내용이 몇가지 나왔는데 기본적인 것에

대한 상세내용이 기억이 나지않아서 복습할 필요성을 느꼇습니다..

 

기본적인것에 대한 용도는 알고 평소에도 잘활용하지만 내부가 어떻게 흘러가는지,

생명주기가 있다면 어떤 순서이고 등등을 다시 회상할겸

Kotlin부터 java, os지식까지 차근히 정리해가보도록 하겠습니다.

 

Object

 

https://kotlinlang.org/docs/object-declarations.html#semantic-difference-between-object-expressions-and-declarations

 

Object expressions and declarations | Kotlin

 

kotlinlang.org

Kotlin 공식문서를 참고해서 작성했습니다.

 

object 는 크게 2가지로 사용합니다.

- 객체 표현식

- 객체 선언

   - 동반 객체 (객체 선언을 클래스 내부에서 사용한것)

 

1. 객체 표현식

객체 표현식은 익명 클래스의 객체, 즉 명시적으로 선언되지 않은 class를만듭니다.

 

1) 익명 객체 사용하기

객체 표현식은 object키워드 시작 합니다.

특별한 상속없이 object 만 필요한 경우 아래과 같이 중괄호로 구문을  작성합니다.

val helloWorld = object {
    val hello = "Hello"
    val world = "World"
    // object expressions extend Any, so `override` is required on `toString()`
    override fun toString() = "$hello $world" 
}

 

2) 익명 class 상속해서 사용하기

window.addMouseListener(object : MouseAdapter() {
    override fun mouseClicked(e: MouseEvent) { /*...*/ }

    override fun mouseEntered(e: MouseEvent) { /*...*/ }
})

상속되는 익명 클래스의 객체를 만들려면

object: 클래스()  이런식으로 사용합니다.

 

기본적으로 클래스 에서 상속하는 것처럼

이 클래스의 멤버 함수를 구현하거나 오버라이드가 가능합니다.

 

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

interface B { /*...*/ }

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

-부모 클래스에 생성자가 있으면 파라미터를 전달해야합니다.

-object : 뒤에 쉼표(,)로 구분해서 여러 인터페이스를 추가로 상속받아 사용할 수 있습니다.

 

3)return 및 value Type으로 익명 객체 사용

익명 객체가 inline 선언 (함수 또는 속성)이 아니면서 private 로 사용할 때

익명 객체를 return하는 함수 또는 변수를 통해 모든 멤버함수에서 접근 할 수 있습니다.

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

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

익명 객체를 return하는 함수 또는 변수가 public 또는 private inline 인 경우

actual type은 다음과 같습니다.

  • Any 익명 객체에 선언 된 super Type이 없는 경우
  • Type이 정확히 하나있는 경우 익명 객체의 선언 된 Super Type
  • 선언 된 Type이 둘 이상있는 경우 명시적으로 선언 된 Type

위 모든 경우에 익명 객체에 추가 된 멤버에 액세스 할 수 없습니다. 

함수 또는 속성의 actual type으로 선언 된 경우 재정의 된 멤버에 접근 할 수 있습니다.

 

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"
    }
}

4) 익명 객체에서 변수 접근

Object 표현식의 코드는

괄호 {} 를 둘러싸는 범위에서 {}안에 있는 변수에 접근 할 수 있습니다.

fun countClicks(window: JComponent) {
    var clickCount = 0
    var enterCount = 0

    window.addMouseListener(object : MouseAdapter() {
        override fun mouseClicked(e: MouseEvent) {
            clickCount++
        }

        override fun mouseEntered(e: MouseEvent) {
            enterCount++
        }
    })
    // ...
}

 

2. 객체 선언

메모리가 1번만 호출되는 싱글톤 용도로 사용합니다. 

 

object DataProviderManager {
    fun registerDataProvider(provider: DataProvider) {
        // ...
    }

    val allDataProviders: Collection<DataProvider>
        get() = // ...
}

 

객체 선언 이라고하며 항상 object 뒤에 객체 이름이 있습니다.

객체 선언은 표현식이 아니라서 객체 표현식처럼 사용할 수 없습니다.

 

객체 선언의 초기화는 스레드로부터 안전합니다.

아래처럼 Object명.으로 접근이 가능합니다.

DataProviderManager.registerDataProvider(...)

혹은 상속을 받아 사용할 수 있습니다.

object DefaultListener : MouseAdapter() {
    override fun mouseClicked(e: MouseEvent) { ... }

    override fun mouseEntered(e: MouseEvent) { ... }
}

그리고 *중첩 object선언이 가능합니다. (static inner class)

 

2-2. 동반 객체 (Companion Object)

클래스 내부의 객체 선언은 companion object 키워드로 표시 할 수 있습니다.

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

companion object 의 멤버함수는 단순히 생성자 없이

클래스 이름을 사용해서 호출 할 수 있습니다.

 

val instance = MyClass.create()

companion object의 이름은 생략 할 수 있습니다. 이 경우 이름은 Companion이 사용됩니다.

class MyClass {
    companion object { }
}

val x = MyClass.Companion

 

클래스 멤버는 해당 Companion object의 비공개 필드에 접근 할 수 있습니다.

생성자 표시 없이 사용한 클래스 이름은

 

(이름이 지정되었는지 여부에 관계없이)

클래스 내부의 Companion object 에 대한 참조 역할을합니다.

 

class MyClass1 {
    companion object Named { }
}

val x = MyClass1

class MyClass2 {
    companion object { }
}

val y = MyClass2

companion object 의 멤버는 다른 언어에서 static 멤버처럼 보이지만

런타임에서 여전히 실제 객체의 인스턴스 멤버입니다.

 

예를 들어 인터페이스를 구현할 수 있습니다.

interface Factory<T> {
    fun create(): T
}

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

val f: Factory<MyClass> = MyClass

 

그러나 JVM에서는 @JvmStatic주석 을 사용하는 경우

static 메소드 및 필드로 생성 된 companion object의 멤버를 가질 수 있습니다 . 

 

3. 객체 표현식과 객체 선언의 차이

객체 표현식과 객체 선언의 차이는 다음과 같습니다.

  • 객체 표현식은 사용되는 곳에서 즉시 실행 (및 초기화) 됩니다.
  • 객체 선언은 처음 접근 할 때 늦은 초기화 됩니다.
  • companion object 는 해당 class가 load 될 때 초기화되며 Java static initializer 와 일치합니다.

 

마지막으로 특징을 한꺼번에 정리한 블로그도 하나 추천합니다.

https://velog.io/@ditt/Kotlin-Object-%ED%82%A4%EC%9B%8C%EB%93%9C

 

[Kotlin] Object 키워드

Kotlin의 Object 키워드

velog.io

 

코딩은 기본기가 중요하다고 생각합니다. 항상 잊을만하면 복습하는 것은 필수 인것 같네요

이상 kotlin object에 관한 정리 였습니다.