Kotlin Object 키워드

Object keyword

많은 객체 지향 프로그래밍 언어에서 Singleton 패턴을 구현하기 위해서는 보통 클래스 생성자를 private으로 선언하고, private으로 static한 맴버로 인스턴스 저장하여 팩토리 패턴으로 캐시되어 있는 동일한 인스턴스를 반환하는 방법이 전형적인 구현 방법이다.

그런데, Kotlin에서는 명시적으로 static 라는 한정자가 없다. 그럼 Singleton을 구현하려면 어떻게하면 좋을까.

여기에서는 Kotlin의 object 키워드 사용방법과 Singleton 구현에 대해서 소개하겠다.

object에는 3개의 이용 형태(Object 변수 선언, Object 클래스, Companion Object)가 있다.

Object 변수 선언

Object 변수 선언은 클래스의 이름 인스턴스를 생성한다. 원래 클래스의 속성과 메소드를 오버라이드 할 수 있다.

아래의 예에서는 MouseAdapter 클래스를 재정의하고 그 이름 인스턴스를 만들고 addMouseListener()의 인수로 전달한다.

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

Object 클래스(싱글 톤)

Kotlin에서는 클래스에 object 키워드를 사용하여 객체를 선언하면, 유일한 1개의 인스턴스를 가지는 싱글톤 클래스가 만들어 진다.

아래의 예제는 Config라는 싱글톤 객체를 생성하고 있다.

object Config {
    var maxCount = 100
}

fun main() {
    println(Config.maxCount)
}

Output:

100

싱글톤의 메소드는 정적 메소드와 같이 클래스명.메소드명으로 호출할 수 있다.

object Foo {
    fun hello() = println("Hello!")
}

fun main() {
    Foo.hello()
}

Output:

Hello!

다음 예로는, List 데이터를 저장하는 클래스를 구현해 보겠다.

object FruitStore {
    private val fruits = mutableListOf<String>()

    fun add(s: String) {
        fruits.add(s)
    }

    fun printList() {
        for (item in fruits) {
            println(item)
        }
    }
}

fun main() {
    FruitStore.add("Apple")
    FruitStore.add("Orange")
    FruitStore.add("Banana")
    FruitStore.printList()
}

Output:

Apple
Orange
Banana

이 클래스를 사용하는 곳에서는 인스턴스를 생성하지 않고 메서드를 호출하는 것을 볼 수 있다.

Companion Object(정적 메서드)

companion object는 클래스 내에서 정적 메소드를 정의하는데 사용된다.

class Math {
    companion object {
        fun add(a: Int, b: Int) = a + b
    }
}

fun main() {
    println(Math.add(3, 5))
}

Output:

8

다음 예로는, List 데이터를 저장하는 싱글톤 클래스를 구현해 보겠다.

class FruitFactoryStore {
    companion object Factory {
        private val items = mutableListOf<String>()
        fun create(): FruitFactoryStore = FruitFactoryStore()
    }

    fun add(s: String) {
        items.add(s)
    }

    fun printList() {
        for (item in items) {
            println(item)
        }
    }
}

fun main() {
    val store = FruitFactoryStore.create()
    store.add("Apple")
    store.add("Orange")
    store.add("Banana")
    store.printList()
    
    println("-")
    
    val store2 = FruitFactoryStore.create()
    store2.printList()
}

Output:

Apple
Orange
Banana
------
Apple
Orange
Banana

결과를 보면 store2에는 항목을 추가하지 않았는데, 프린트해 보면 데이터가 표시되는 것을 볼 수 있다.

위에 예제에서는 companion object 다음으로 Factory라는 이름을 편의상 붙여지만, 선택 사항이다. 호출 시에도 따로 Factory를 지정할 필요 없이, 그 클래스의 직접적인 메소드인 것처럼 메소드를 호출하면 된다.