반응형

코틀린에는 Java에서 제공하는 static이 없다.

그렇다면 모든 멤버 변수나 메소드에 대해 인스턴스에 결속해서 런타임에 생성돼야만 하는가?

코틀린에서는 static의 대안으로 object라는 키워드를 제공한다.

오브젝트(object)는 자연스러운 싱글톤이다. 여기서 자연스럽다는 말은 다른 언어에서와 같이 행동 패턴으로 구현하는 것이 아니라 언어 차원의 기능으로 나타나는 것을 뜻한다.

싱글톤(Singleton)은 인스턴스가 오직 하나뿐인 타입이며, 코틀린의 모든 object는 싱글톤이다.(여기서 '모든 object'라는 표현을 '모든 객체'나 'Java의 모든 객체의 조상인 Object'와 헷갈리지 말자)

 

object in method

fun main(args: Array<String>) {
    val expression = object {
        val bar = "example"
        fun foo(): String {
            return "object"
        }
    }
    
    println("${expression.foo()} ${expression.bar}")
}
object example

이 경우 익명의 object로 생성할 수 있는 인스턴스는 expression에 대입된 인스턴스, 오직 하나다.

한 가지 제한이 있다면, 타입이 없는 오므젝트 표현식은 메소드 내부에서 로컬로 사용되거나 클래스 내에서 private 범위로만 사용할 수 있다.

 

object in class

class Outer {
    val internal = object {
        val property = "property"
    }
}

fun main(args: Array<String>) {   
    val outer = Outer()
    println(outer.internal.property)	// compile error: unresolved reference: property
}

이처럼 Outer 인스턴스에서 internal까지는 접근 가능하지만 object 내의 접근은 class-private이기에 외부에서 사용 불가능하다.

 

그럼 이 object라는 것은 전역적으로 쓸 수 없을까? 싱글톤이라며?

object는 꼭 클래스 내에서 정의될 필요도 없으며, 이름도 가질 수 있다.

object Foo {
    fun bar(): String {
        return "foo.bar"
    }
}

fun main(args: Array<String>) {
    println(Foo.bar())
}
foo.bar

 

object는 싱글톤이다. 그렇기 때문에 Foo object에 대해 인스턴스화 할 필요 없다.

 

 

그러면, 클래스나 인터페이스 내부에서 생성된 오브젝트는 반드시 그 클래스의 인스턴스와 함께 사용되어야 할까? 그건 java의 static과는 좀 다른데..?

드디어 제목에서 언급한 static에 대해 대안이 등장할 차례다. 그건 바로 companion object이다.

 

companion object

class Cake(val flavor: String) {
    companion object {
        fun strawberry(): Cake {
            return Cake("딸기")
        }
        
        fun cheese(): Cake {
            return Cake("치즈")
        }
    }
}

fun main(args: Array<String>) {
    val strawberryCake = Cake.strawberry()    // Cake 클래스 인스턴스 없이 사용 가능
    val chesseCake = Cake.cheese()
}

 

이렇게 compinon object를 사용할 경우 java에서 static을 쓴 것처럼 class의 인스턴스화 없이 오브젝트 내의 메소드를 직접 사용할 수 있다.

단, 주의할 점은 java에서는 인스턴스에서 static 변수나 메소드를 참조하는 것이 가능했지만, 코틀린에서는 인스턴스에서 companion object 내의 변수나 메소드를 접근할 수 없다.

 

다양한 사용법

companion object를 사용하는 방법은 다양하다. 앞선 예제의 Cake에 대해 아래와 같은 방법들이 있다.

class Cake(val flavor: String) {
    fun printCake() {
        println(flavor)
    }
    companion object {
        fun strawberry(): Cake {
            return Cake("딸기")
        }

        fun cheese(): Cake {
            return Cake("치즈")
        }
    }
}

fun main(args: Array<String>) {
    val factory: Cake.Companion = Cake.Companion  // 익명의 companion object를 가리킴
    factory.strawberry().printCake()
    val factory2: Cake.Companion = Cake    // Cake는 companion object, Cake()는 instance!
    factory2.cheese().printCake()
}
딸기
치즈

 

마지막으로, 아래와 같이 companion object에도 이름을 붙여줄 수 있다.

class Cake(val flavor: String) {
    fun printCake() {
        println(flavor)
    }
    companion object Factory {
        fun strawberry(): Cake {
            return Cake("딸기")
        }

        fun cheese(): Cake {
            return Cake("치즈")
        }
    }
}

fun main(args: Array<String>) {
    val factory: Cake.Factory = Cake.Factory
    factory.strawberry().printCake()
    val factory2: Cake.Factory = Cake
    factory2.cheese().printCake()
}
딸기
치즈

 

주의할 점은, companion object는 class 내에 오직 한 개만 만들 수 있다. 그리고 companion object에 이름이 있을 경우 Cake.Companion과 같이 Companion으로 부르는 것이 안 되고, 지정한 이름으로 호출해야 한다.

companion object의 경우 자연스럽게 인스턴스가 하나만 생성되기 때문에 이를 이용해서 싱글톤을 구현하면 되겠다.

반응형
반응형