Object Expressions and Declarations

2018, Jul 08    

다음은 코틀린 Ref문서중 Object Expressions and Declarations를 번역한 글이다.(의역 포함)

때때로 우리는 클래스를 명시적으로 선언하지 않고 약간 수정만한 객체가 필요한 경우가 있다. JAVA는 이런 경우를 익명의 inner 클래스로 처리한다.

Kotlin은 객체식(object expressions)과 객체 선언으로 이 개념을 약간 일반화한다.

Object expressions (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
}

만약에 이렇다할 의미 있는 슈퍼타입 없이, 오로지 하나의 object가 필요하다면, 아래와 같이 간단한 방법으로 만들 수 있다.
fun foo() {
    val adHoc = object {
        var x: Int = 0
        var y: Int = 0
    }
    print(adHoc.x + adHoc.y)
}


익명객체는 오로지 local안에서나 private선언으로만 타입으로서 사용될 수 있다.
익명객체를 public 함수의 리턴타입이나 프로퍼티의 타입으로 쓴다면,
그 함수나 프로퍼티의 실제 타입은 익명 클래스의 상위타입이 된다.
혹은 익명 객체에 어떤 상위 타입도 선언하지 않은 경우 Any타입으로 인식하게 된다.
익명 클래스 안에 추가된 멤버는 접근 할수 없다.

class C {
    // Private function, so the return type is the anonymous object type
    private fun foo() = object {
        val x: String = "x"
    }

    // Public function, so the return type is Any
    fun publicFoo() = object {
        val x: String = "x"
    }

    fun bar() {
        val x1 = foo().x        // 실행가능
        val x2 = publicFoo().x  // 'x'에 접근 안됨
    }
}

자바의 익명 inner class와 마찬가지로 oject expressions안의 코드들은 둘러싼 범위의 변수에 접근 할 수 있다.

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 (Object 선언)

싱글톤은 여러 경우에 유용 할 수 있으며 코틀린(Scala 이후)은 싱글톤을 선언하기 쉽다.

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

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

이를 오브젝트 선언(object declarations)이라고하며 오브젝트 키워드 다음에 항상 이름이 있다. 변수 선언과 마찬가지로 객체 선언은 표현식이 아니며 대입문(=)의 오른쪽 사이드에 올 수 없다.

객체 선언의 초기화는 스레드로부터 안전하다.(thread-safe)

객체를 참조하기 위해 이름을 직접 사용한다.

DataProviderManager.registerDataProvider(...)

이러한 객체는 수퍼 유형을 가질 수 있습니다.

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

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

참고 : 객체 선언은 로컬일 수 없으며 (즉 함수 내에 직접 중첩 될 수 있지만-nested directly) 다른 객체선언 또는 내부 클래스가 아닌 클래스(non-inner class)에 중첩 될 수 있습니다.

Companion Objects(동반자 개체)

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

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객체의 멤버가 다른 언어의 정적 멤버처럼 보이더라도 런타임에는 여전히 실제 객체의 인스턴스 멤버이며 예를 들어 인터페이스를 구현할 수 있다.

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

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

그러나 JVM에서 @JvmStatic 어노테이션을 사용하는 경우 companion객체의 멤버를 실제 정적 메서드 및 필드로 생성 할 수 있다. 자세한 내용은 Java 상호 운용성 섹션(Java interoperability)을 참조하십시오.

Object Expressions과 Declarations사이의 의미론적 차이

객체 표현과 객체 선언 사이에는 중요한 의미상의 차이가 있다.

  • 객체 표현은 사용되는 곳에서 즉시 실행되고 초기화된다.
  • 객체 선언은 처음 액세스 할 때 초기화됨으로 지연 초기화된다.
  • companion object는 Java static initializer와 의미상 일치하는 대응하는 클래스가 load(참조)되었을 때에 초기화된다. –> (이해가 안가서 원문을 붙임 : a companion object is initialized when the corresponding class is loaded (resolved), matching the semantics of a Java static initializer.)