꿈소년의 개발 이야기

Kotlin Object - Object Expressions 코틀린 객체 표현식 본문

Android Development

Kotlin Object - Object Expressions 코틀린 객체 표현식

꿈소년 2021. 7. 21. 23:19
반응형

Object는 일부 클래스에 대해 새로운 하위 클래스를 명시적으로 선언하지 않고, 그 클래스를 조금 일부를 변경한 객체를 만들어야 할 때 사용합니다.

Object Expressions 또는 Object Declarations를 사용하여 이를 처리할 수 있습니다.

그 중에서 Object Expressions 에 대해 적어봅니다. 코틀린 공식 문서를 참고했습니다.

객체 표현식

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

이러한 클래스는 일회용으로 유용합니다.

처음부터 정의하거나, 기존 클래스에서 상속하거나, 인터페이스를 구현할 수 있습니다.

익명 클래스의 인스턴스는 이름이 아닌 표현식으로 정의되므로 익명 객체 라고도 합니다.

처음부터 익명 객체를 생성하기

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

부모 타입을 가지지 않는 단순 익명 객체를 만들고자 한다면, 아래 코드와 같이 작성합니다.

//hello 익명 객체를 출력하면 message 가 출력됩니다.
val hello = object {
    val message = "hello"
    override fun toString() = message
}

부모 타입으로 부터 상속받아 익명 객체 생성하기

익명 객체가 상속을 받는다고 보면 됩니다. 코틀린에서의 상속 방식과 동일하게 표현됩니다.

부모 클래스의 멤버들을 구현하거나 override 함수를 처리합니다.

// hello 익명 객체는 Hello 를 출력할 것이다.
open class Hi {
    open val message = "Hi"
}

val hello = object : Hi() {
    override fun toString() = message
}

만약에 부모 타입이 생성자를 가지고 있으면, 적절한 생성자 파라미터를 넘겨줄 수 있습니다.

인터페이스와 부모 클래스를 동시에 상속하는 것이 가능합니다.

interface Korean {
    fun hello()
}

open class Hi {
    open val message = "Hi"
}

// hello 익명 객체는 hello() 를 호출하면 한국어 인사를 출력할 것이다.
val hello = object : Hi(), Korean {
    override fun hello() {
        println("안녕하세요")
    }

    override fun toString() = message
}

익명 객체를 리턴 값이나 값 타입으로 사용하기

익명 객체는 프로퍼티나 함수 타입으로서 사용할 수 있습니다. 단, 인라인 선언 된 경우는 해당되지 않습니다.

이렇게 선언할 경우, 함수나 프로퍼티는 해당 익명 객체 내부의 멤버에 접근 가능합니다.

함수나 프로퍼티로 된 익명 객체의 실제 리턴 타입은 아래와 같이 정해집니다.

  • Any : 익명 객체에 아무런 부모 타입도 선언되지 않은 경우.
  • 익명 객체의 부모 타입 : 익명 객체에 정확한 부모 타입이 하나 있는 경우.
  • 명시적으로 선언된 타입 : 익명 객체에 둘 이상의 부모 타입이 선언된 경우 명시적으로 선언된 타입.

익명 객체의 멤버들은 접근이 불가능합니다.

오버라이드 된 맴버들은 모두 접근 가능합니다. 단, 실제 타입에 선언된 경우에만 해당합니다.

interface American

interface Korean {
    fun hello()
}

class Person {
    // 익명 객체가 Any 타입을 가진다.
    private fun getObjectPrivately() = object {
        val name = "private name"
    }

    // 익명 객체가 Any 타입을 가진다.
    fun getObject() = object {
        val name = "Any"
    }

    // 익명 객체가 American 타입을 가진다.
    fun getObjectA() = object : American {
        val name = "American"
    }

    // 익명 객체가 Korean 타입을 가진다. 명시적 리턴 타입이 선언되어야 한다.
    fun getObjectK(): Korean = object : Korean, American {
        val name = "Korean-American"
        override fun hello() {
            println(name)
        }
    }

    fun print() {
        // private 인 경우, 내부에서 익명 객체의 멤버에 접근 가능하다.
        println(getObjectPrivately().name)

        // 오버라이드 된 함수는 내부 및 외부에서도 접근 가능하다.
        println(getObjectK().hello())

        // 아래 코드에서는 모두 익명 객체 맴버에 접근이 불가능.
        println(getObject().name) // ❌
        println(getObjectA().name) // ❌
        println(getObjectK().name) // ❌
    }
}

// 외부에서 호출하는 경우, 익명 객체의 맴버는 접근 불가. 단 오버라이드 함수는 가능.
val p = Person()
println(p.getObject().name) // ❌
println(p.getObjectA().name) // ❌
println(p.getObjectK().name) // ❌
println(p.getObjectK().hello()) // 접근 가능하다.

특정 범위 내에서의 익명 객체의 변수 접근

아래와 같은 함수 범위 내에서 해당 함수 내에 선언된 변수에 대해 접근이 가능합니다.

fun main(args: Array<String>) {
    counting(Counter())
}

fun counting(counter: Counter) {

    var count = 0

    counter.addCountListener(object : CountListener {
        override fun countPlus() {
            // 접근이 가능함.
            count++
        }

        override fun countMinus() {
            // 접근이 가능함.
            count--
        }
    })

    counter.countListener.countPlus()
    counter.countListener.countPlus()
    counter.countListener.countPlus()
    
    // 총 3번의 호출로 3이 출력.
    println(count)
}

다음에는 Object declarations 에 대해 알아보겠습니다. 🙏🙏🙏🙏🙏🙏🙏