본문으로 바로가기

[Kotlin] 코틀린 object

category 개발이야기/Kotlin 2018. 4. 23. 00:30
반응형

이 글은 Kotlin In Action을 참고 하였습니다.

더욱 자세한 설명이나 예제는 직접 책을 구매하여 확인 하시기 바랍니다

코틀린은 object란 키워드를 사용합니다.

자바에는 이 키워드가 없죠.

약간 생소할 수 도 있는 이 키워드는 java의 어떤 개념과 매칭되는지 알아봅니다.


4.4 object 키워드, 클래스의 선언과 인스턴스 생성

코틀린에는 static 개념이 없습니다.
사실 개념이 없다기 보단 static keyword가 없기 때문에 java의 static 개념을 코틀린에서 어떻게 표현해야 하는지를 중점적으로 살펴보겠습니다.

  • 싱글턴을 정의하는 방법
  • 동반객체 companion object를 이용한 팩토리 메서드 구현
  • 무명클래스(익명 클래스)의 선언

위 세가지를 object란 keyword를 이용해서 표현합니다.

4.4.1 싱글턴 (Singleton)

코틀린에서는 object를 이용하여 클래스를 정의함과 동시에 객체를 생성할 수 있습니다.
말 그대로 싱글턴을 쉽게 구현할 수 있습니다.
object Payroll {
    val allEmplyoees = arrayListOf()
    fun calculateSalary() {
        for (person in allEmplyoees) {
            ....
        }
    }
}

급여를 계산하는 함수 입니다.

object로 선언하면 클래스 선언과 동시에 객체가 생성됩니다.

Payroll.allEmplyoees .add(Person("홍길동","김말똥")
Payroll.calcualteSalaray()

객체 이름을 통해 property나 메서드에 직접 접근할 수 있습니다.


object 객체 역시 다른 class를 상속하거나 interface를 구현할 수 있습니다.

object CaseInsensitiveFileComparator : Comparator {
    override fun compare(file1: File, file2: File): Int {
        return file1.path.compareTo(file2.path,
                ignoreCase = true)
    }
}

fun main(args: Array<String>) {
    println(CaseInsensitiveFileComparator.compare(
        File("/User"), File("/user")))
    val files = listOf(File("/Z"), File("/a"))
    println(files.sortedWith(CaseInsensitiveFileComparator))
}

comparator에 대한 예제 입니다.

comparator는 여러개를 구현할 필요가 없기 때문에 singleton으로 하나만 만들고 사용하면 편리합니다.

직접 compare를 호출해서 사용할 수 도 있고, list에서 sortedWith 함수를 이용하여 객체 자체를 넘겨줄 수도 있습니다.

여기서 sortedWith는 리스트를 정렬하는 함수 입니다.


만약 Person 객체를 정렬하는 comparator를 Person 객체 내부에 구현하고 싶다면 중첩된 class 형태로 구현해도 됩니다.

외부 class의 객체를 여러개 생성하더라도 내부에 존재하는 object는 단일 객체만 존재 합니다.

즉 class 밖에서 선언되나, class 안에서 선언되나, singleton으로 생성됩니다.

data class Person(val name: String) {
    object NameComparator : Comparator {
        override fun compare(p1: Person, p2: Person): Int =
            p1.name.compareTo(p2.name)
    }
}

fun main(args: Array<String>) {
    val persons = listOf(Person("Bob"), Person("Alice"))
    println(persons.sortedWith(Person.NameComparator))
}


Java에서 object 객체를 호출시에는 INSTANCE란 이름을 통해 호출합니다.

Person.NameComparator.INSTANCE.compare(new Person("고길동"), new Person ("도우너"));


4.4.2 companion object

코틀린에서는 static을 지원하지 않는대신 top-level function을 통해 같은 효과를 낼 수 있습니다.
단, top-level function은 class 내부에 선언된 private property에는 접근할 수 없는 제한을 받습니다.

이를 해결하기 위해서 companion object란 개념이 존재합니다.
클래스의 인스턴스와 상관없이 호출해야 하지만 class의 내부 정보에 접근할수 있는 함수가 필요할때 companion object를 class 내부에 선언합니다.
java로 따지자면 class 내부에 static 함수를 넣는다고 생각하면 됩니다.

class A {
    companion object {
        fun bar() {
            println("Companion object called")
        }
    }
}

fun main(args: Array<String>) {
    A.bar()
}

클래스 내부에 선언된 companion object는 호출할때 클래스 이름으로 바로 호출할 수 있습니다.

(java의 static 함수와 동일한 형태입니다.)

companion object는 외부 클래스의 private property에도 접근이 가능하기에, factory method를 만들때 적합합니다.

class User private constructor(val nickname: String) {
    companion object {
        fun newSubscribingUser(email: String) =
            User(email.substringBefore('@'))

        fun newFacebookUser(accountId: Int) =
            User(getFacebookName(accountId))
    }
}

fun main(args: Array<String>) {
    val subscribingUser = User.newSubscribingUser("bob@gmail.com")
    val facebookUser = User.newFacebookUser(4)
    println(subscribingUser.nickname)
}

user는 private constructor를 가지기 때문에 외부에서 생성할 수 없습니다.

다라서 외부에서는 companion으로 제공되는 factory method를 이용해서만 객체를 생성할 수 있도록 제한할 수 있습니다.


4.4.3 companion object의 사용

companion object는 클래스 내부에 정의된 일반 객체입니다.

따라서 아래와 같은 작업이 가능합니다.

  • companion object에 이름 명명
  • companion object 내부에 확장 함수나 property 정의
  • 인터페이스 상속
class Person(val name: String) {
    companion object Loader {
        fun fromJSON(jsonText: String): Person = ....
    }
}

fun main(args: Array<String>) {
    person1 = Person.Loader.fromJSON("{name:'hong'}")
    person2 = Person.fromJSON("{name:'kim'}")
}


companion object에 이름을 붙일수 있으며, 이름을 통해서 호출할 수 도 있고, 그냥 호출할 수 도 있습니다.


interface JSONFactory<T> {
     fun fromJSON(jsonText: String): T
}

class Person(val name: String) {
    companion object: JSONFactory{
       override fun fromJSON(jsonText: String): Person = ....
    }
}

fun loadFromText<T>(factory:JSONFactory<T>): T {
   ...
}

fun main(args: Array<String>) {
    loadFromText(Person)
}

위 예제처럼 companion object가 특정 interface를 구현할 수도 있고, 이 interface를 넘겨줄때는 외부 class 이름을 사용합니다.


자바에서 호출시 Companion을 붙여서 사용해야 합니다.

Person.Companion.fromJSON("....");

만약 companion에 이름이 있다면 해당 이름이 대신해서 쓰입니다.

자바에서 companion object로 명명된 함수나 property를 static처럼 호출해서 쓰고 싶다면 @JvmStatic 이나 @JvmField 어노테이션을 함수나, property 앞에 붙여야 합니다.

또한 companion object에 대해서 Extension function을 만들어 추가할 수도 있습니다만, 자세한건 책을 사서 확인하시면 됩니다~


4.4.4 익명클래스의 구현

익명클래스를 구현할때, object keyword를 사용합니다.
interface ClickListener {
    fun onClick()
}

fun main(args: Array) {
    setClickAction(object : ClickListener {
        override fun onClick() {
        	println("clicked!!!")
        }
    }
    )
}

fun setClickAction(clickListener: ClickListener) {
    clickListener.onClick()
}

여기서 익명 클래스는 singleton이 아닙니다. 따라서 호출시 매번 객체가 생성된다는 점과 익명클래스 내에서는 외부 클래스의 변수에 접근하여 값을 수정할 수도 있습니다.(자바와는 다른점입니다. 자바는 익명클래스에서 접근시 무조건 final이어야만 합니다.

반응형