Programming Language/Kotlin

[Koltin] Object Keyword. (싱글톤 패턴, 익명 함수)

개발왕 금골드 2023. 2. 8. 22:08
반응형
  • 경우에 따라 새 하위 클래스를 명시적으로 선언하지 않고 일부 클래스를 약간 수정한 개체를 만들어야 한다. 코틀린은 객체 표현식과 객체 선언으로 이를 처리할 수 있다.

Object expressions

  • 객체 표현식은 익명 클래스, 즉 클래스가 명시적으로 선언되지 않은 클래스의 객체를 만든다. 이러한 클래스는 일회용으로 사용하기 유용하다.
  • 처음부터 정의하거나 기존 클래스에서 상속하거나 인터페이스를 구현할 수 있다.
  • 익명 클래스의 인스턴스는 이름이 아닌 식에 의해 정의되므로 익명 객체라고도 한다.


Creating anonymous objects from scratch

  • 체 표현식은 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"
}

// result
// Hello World


Inheriting anonymous objects from supertypes

  • 어떤 유형을 상속받는 익명 클래스의 객체를 만들려면 object와 콜론(:) 뒤에 유형을 선언한다.
  • 그런 다음 이 클래스 구성원을 상속받는 것처럼 구현하거나 재정의한다.
window.addMouseListener(object : MouseAdapter() {
    override fun mouseClicked(e: MouseEvent) { /*...*/ }

    override fun mouseEntered(e: MouseEvent) { /*...*/ }
})
  • 슈퍼타입에 생성자가 있는 경우 적절한 생성자 매개변수를 전달한다.
  • 콜론 뒤에 여러 개의 슈퍼타입을 쉼표로 구분하여 목록으로 지정할 수 있다.
open class A(x: Int) {
    public open val y: Int = x
}

interface B { /*...*/ }

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


Using anonymous objects as return and value types

  • 익명 객체가 인라인 선언이 아닌 로컬 또는 private 유형의 객체로 사용될 때, 모든 구성원은 이 함수 또는 프로퍼티를 통해 액세스 할 수 있다.
class C {
    private fun getObject() = object {
        val x: String = "x"
    }

    fun printX() {
        println(getObject().x)
    }
}
  • 만약 이러한 함수 혹은 프로퍼티가 public or private inline으로 선언되었다면, 실제 타입은 다음 상황에 따라 결정된다.
    • 만약 선언된 슈퍼타입이 없다면 익명 객체의 타입은 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"
    }
}


Accessing variables from anonymous objects

  • 객체 표현식 안에 있는 코드는 일정 범위 안에 있는 변수에 접근할 수 있다.
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++
        }
    })
    // ...
}


Object declarations

  • Singleton 패턴은 매우 유용한데, 코틀린은 object 키워드를 사용하여 이를 쉽게 만들 수 있다.
  • 객체 선언은 표현식이 아니므로 할당문 오른쪽에 선언할 수 없다.
  • 객체 선언의 초기화는 스레드에 안전하며 첫 번째 액세스에서 수행된다.
  • 객체 선언은 로컬에서 선언될 수 없지만 (즉, 함수 내에 직접 중첩 불가.) 다른 객체 선언이나 내부 클래스가 아닌 클래스로 중첩될 수 있다.
object DataProviderManager {
    fun registerDataProvider(provider: DataProvider) {
        // ...
    }

    val allDataProviders: Collection<DataProvider>
        get() = // ...
}
  • 객체를 참조하려면 직접 참조한다.
DataProviderManager.registerDataProvider(...)
  • 슈퍼타입이 존재할 수 있다.
object DefaultListener : MouseAdapter() {
    override fun mouseClicked(e: MouseEvent) { ... }

    override fun mouseEntered(e: MouseEvent) { ... }
}
  • Kotlin에서 일반 객체 선언을 인쇄할 때 문자열 표현에 객체의 이름과 해시가 모두 포함되어 있다.
object MyObject

fun main() {
    println(MyObject) // MyObject@1f32e575
}
  • data class처럼 data object로 선언하면 toString 함수에 대한 구현을 수동으로 구현하지 않고도 적절한 형식의 문자열 표현을 얻을 수 있다. (Kotlin 1.9 버전에서 사용 가능하지만 실험적인 기능.)
data object MyObject

fun main() {
    println(MyObject) // MyObject
}
  • data object 기능은 sealed class와 함께 사용하기 유용하다.
sealed class ReadResult {
    data class Number(val value: Int): ReadResult()
    data class Text(val value: String): ReadResult()
    data object EndOfFile: ReadResult()
}

fun main() {
    println(ReadResult.Number(1)) // Number(value=1)
    println(ReadResult.Text("Foo")) // Text(value=Foo)
    println(ReadResult.EndOfFile) // EndOfFile
}


Companion objects

  • companion 키워드를 사용하여 멤버를 전역으로 사용할 수 있고 쉽게 접근할 수 있다.
  • 해당 클래스가 로드될 때 초기화된다.
  • const val로 선언된 상수는 static 변수라고 할 수 있다. (혹은, @JvmStatic or @JvmField 어노테이션이 붙은 함수 및 프로퍼티)
class MyClass {
    companion object Factory {
        fun create(): MyClass = MyClass()
    }
}

val instance = MyClass.create()
  • 다른 언어에서의 static 멤버와 비슷하지만 런타임에서는 실제 객체의 인스턴스 멤버이기 때문에 인터페이스 구현이 가능하다.
  • 그러나 만약 @JvmStatic 어노테이션을 사용하였다면 JVM에서 실제 정적 메서드 및 필드로 생성된 객체의 멤버를 가질 수 있다. Java와의 상호 운용성에 대한 이해가 필요하다.
interface Factory<T> {
    fun create(): T
}

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

val f: Factory<MyClass> = MyClass


Semantic difference between object expressions and declarations

  • 객체 표현식은 사용되는 곳에서 즉시 실행(초기화)된다.
  • 객체 선언은 처음 액세스되었을 때 lazy 초기화된다.
  • 객체 선언은 Java로 변환된 파일 내부에 object file name 타입의 static INSTANCE 객체가 생성된다. (Singleton)
  • companion object는 해당 클래스가 로드될 때 초기화된다.
  • companion object 내에서 선언된 함수 및 프로퍼티는 Java로 변환된 파일 내부에 생성되는 static class Companion을 통해 접근이 가능하다. 즉, 함수 및 프로퍼티 자체가 static은 아니다.

 



Kotlin Docs 번역
참고자료 :
https://kotlinlang.org/docs/object-declarations.html

 

Object expressions and declarations | Kotlin

 

kotlinlang.org

 

반응형

'Programming Language > Kotlin' 카테고리의 다른 글

[Kotlin] Sealed Class  (0) 2023.02.19
[Kotlin] Interfaces  (0) 2023.02.12
[Kotlin] forEach에서 break & continue  (0) 2022.12.05
[Kotlin] 진수 변환. 알고리즘.  (5) 2021.08.25
[Kotlin] 비동기 프로그래밍 기술  (0) 2021.06.19