본문 바로가기

[Kotlin] 코틀린 기본 - object / Companion Object(동반 객체)

Kotlin - object 키워드 + Companion Object (동반 객체)

 

 

Kotlin object 키워드

  • object는 흔히 JAVA에서 사용하는 무명 내부 클래스(anonymous inner class) 처럼 사용할 수 있습니다
  • object 키워드는 클래스를 정의하면서 객체를 생성하는 키워드

 

 


object - 싱글톤(SingleTon)

Kotlin에서는 object 키워드를 사용함으로 별다른 정의없이 싱글톤(Singleton) 구현을 지원해줍니다

JAVA에서는 클래스 내부에 Static객체로 한번만 할당해주는 코드가 Kotlin에서는 class 키워드 대신 object 키워드를 사용하면 static객체에 할당하는 것처럼 자동으로 생성해줍니다

 

// UserRepo 클래스 싱글톤 구현
object UserRepo {
    val allUser = arrayListOf<User>()
    
    fun getUser() {
    	// ...
    }
}

ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
// 사용방법
UserRepo.getUser()
UserRepo.allUser.add(User("홍길동",Job("IT",Address("Seoul"))))


위 코드에서 사용방법을 보면 UserRepo 인스턴스를 따로 생성하지 않고 싱글톤 사용방법 처럼 바로 사용이 가능하게 됩니다

 

 


object 생성자

object 객체(싱글톤)는 주 생성자 / 부 생성자 모두 사용할 수 없습니다

// ERROR, object는 생성자 사용이 불가능
object UserRepo(val name:String) {
	//...
}


Object는 싱글톤 객체이기 때문에 생성자 자체를 사용할 수 없습니다.
개발자가 원하는 시점에 인스턴스를 생성하는 방식이 아니기 때문에 생성자 param을 전달할 수도 없기 때문입니다 

 

 


object 키워드 자바에서 접근

JAVA에서는 싱글톤을 구현하면 관례적으로 INSTANCE를 사용합니다

Kotlin의 object에서 생성되는 싱글톤 객체도 INSTANCE의 이름을 가진 변수를 생성하기 때문에 JAVA에서 사용할 경우 INSTANCE로 접근해서 사용이 가능

 

// Kotlin의 object객체를 JAVA에서 사용하기
UserRepo.INSTANCE.getUser();


UserRepo INSTANCE라는 멤버변수에 접근하는 것과 같은 의미입니다

 

 

Companion object "동반 객체"

Kotlin에는 static이 없지만, 대신 Package 수준의 최상위 함수객체 선언을 사용할 수 잇습니다

최상위 함수 - static 메서드를 대신할 수 있는 함수

객체 선언 - 최상위 함수가 접근할 수 없는 클래스 내에 private 멤버 변수에 접근할 때 사용하는 객체

companion object {...}로 객체 선언 방법을 사용할 수 있습니다

companion object (동반 객체)는 클래스 내에 하나만 생성할 수 있습니다

class A private constructor(val name: String) { // 클래스 A의 주 생성자는 private로 접근 제한

    companion object {
        fun bar() : A {
            return A("zero")	// companion object의 bar() 메서드를 통해 private 생성자 접근 가능
        }
    }
}

ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
// 사용 방법

var a = A("TEST")	// ERROR, 주 생성자는 private 접근제한자로 설정되어 호출 불가능

var b = A.bar()		// SUCCESS, 동반객체의 bar() 메서드를 통해 private 주생성자에 접근이 가능


위 예시는 A클래스에 주 생성자를 private 접근제한자를 사용해 외부에서 생성자에 접근이 불가능하게 선언했습니다.

위 상황에서 companion object(동반 객체)의 bar() 메서드를 통해 private 주 생성자를 호출 할 수 있게 되었습니다

 

companion obejct에 이름을 붙여 사용할 수 도 있습니다

class A private constructor(val name: String) { // 클래스 A의 주 생성자는 private로 접근 제한

    companion object B {
        fun bar() : A {
            return A("zero")	// companion object의 bar() 메서드를 통해 private 생성자 접근 가능
        }
    }
}

ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
// 사용 방법

A.bar()		// 동반객체 이름 생략
A.B.bar()	// 동반객체 이름 사용
B.bar()		// 클래스명 생략, 동반객체 이름만 사용


위 예시는 companion object(동반 객체)에 B라는 이름을 선언했습니다

동반객체에 이름을 붙였다고 무조건 이름을 사용해야할 필요는 없습니다. 생략도 가능하기 때문에 기존처럼

이름이 없는 동반객체와 똑같이도 사용이 가능합니다

위 코드에서는 3가지의 경우가 있는데 모두 사용가능한 코드입니다

 

 


동반 객체 + 팩토리 패턴

companion object(동반 객체)는 팩토리 패턴을 구현하는데 효과적입니다

클래스를 생성할 때 여러 생성자(constructor)를 만들어서 객체를 생성할 수 있지만, 생성자가 많아지면 복잡해지고 헷갈릴 때가 많아집니다

이럴 경우, 팩토리 패턴을 사용하면 클래스를 생성할 때 어떤 목적으로 만들지 생성자를 선택하는데 있어 도움이 될 수 있습니다

private 생성자인 클래스를 만들고 companion object 블럭에서 User()를 생성하는 팩토리를 구현한 예시입니다

 

class User private constructor(val name: String) { //private 주 생성자

    companion object {
        // 이메일(email)을 기준으로 아이디를 분리해서 User 인스턴스 생성
        fun newSubscribingUser(email:String) = User(email.substringBefore("@"))
        // ID를 사용해서 User 인스턴스 생성
        fun newFacebookUser(id:Int) = User("${id}")
    }
}

ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
// 사용 방법

User.newSubscribingUser("날고싶은개발자@gmail.com")
User.newFacebookUser(1)


: 위와 같이 팩토리 패턴으로 사용하면 User 인스턴스를 생성할 때 SubscribingUser / FacebookUser 클래스가 따로 존재하는 경우에도 필요에 맞는 클래스를 생성하여 리턴할 수 있습니다

만약 하위클래스에서 override가 필요할 경우에는 companion object를 사용할 수 없습니다

동반 객체는 오버라이드가 불가능하기 때문에, 이 경우에는 여러 생성자를 생성하고 상속받은 하위클래스에서 부모의 생성자 중에 골라서 사용하는게 효율적입니다

open class User {
    val name:String

    // 보조생성자 1 - String 인자
    constructor(email: String) {
        this.name = email.substringBefore("@")
    }

    // 보조 생성자 2 - Int 인자
    constructor(id:Int) {
        this.name = "${id}"
    }
}

// 상속이 필요한 경우 동반객체는 상속이 불가능하기에 여러개의 보조 생성자를 생성하는 편이 효율적
class UserTest : User {
    constructor(email: String) : super(email)	// 보조 생성자 1 사용
    constructor(id: Int) : super(id)		// 보조 생성자 2 사용
}

 

 


동반 객체의 인터페이스(interface) 구현

companion object(동반 객체)도 인터페이스를 구현할 수 있습니다

// JSONFactory 인터페이스 - 추상메서드 fromJSON 포함
interface JSONFactory {
    fun fromJSON(jsonText: String) : Person
}

class Person(val name: String) {

    // 동반 객체에 JSONfactory Interface 구현해서 사용
    companion object : JSONFactory{
        // 추상메서드 재정의
        override fun fromJSON(jsonText: String): Person {
            return Person(jsonText)
        }
    }

}


동반객체가 interface를 구현 시 컴파일 시점에 Person.Companion으로 바뀌고 아래 loadFromJSON(...) 함수에 사용됩니다

fun loadFromJSON(factory: JSONFactory) : T {
    //... }

ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
// 사용 방법
loadFromJSON(Person)

 

 


동반 객체 확장함수 사용

동반 객체에 확장함수(Extends Function)를 사용하려면 비어있는 동반 객체를 선언하고 확장함수를 선언합니다

class Person(val name: String) {

    // 동반 객체가 JSONfactory Interface 구현해서 사용
    companion object : JSONFactory{
        override fun fromJSON(jsonText: String): Person {
            return Person(jsonText)
        }
    }

}

// 동반 객체에 확장함수 방법
fun Person.Companion.TestLog(){
    Log.d(TAG, "TestLog 메서드")
}

// 일반 확장함수 방법
fun Person.TestLog(){
    Log.d(TAG, "TestLog 메서드")
}

ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
// 사용 방법

Person.Companion.TestLog()

 

 


무명 객체 (anonymous object)

무명 객체는 주로 JAVA에서 View의 클릭 리스너(setOnClickListener)를 등록할 때 많이 사용하는 방법으로
new View.setOnClickListener() { ... } 의 형식으로 무명 객체를 등록했습니다

Kotlin에서는 object를 사용해 무명 객체를 사용할 수 있습니다
object를 클래스에 사용하는게 아닌 객체에 사용할 경우에는 매번 새로운 인스턴스를 생성하게 됩니다
그리고 object 블럭 내에서 로컬 변수 값에 접근할 수 있습니다

var counter = 10

// OnClickListener의 무명 객체 생성, object객체가 OnClickListener를 구현
btn.setOnClickListener(object : OnClickListener {
    override fun onClick(v: View?) {
        counter++	// 로컬 변수에 접근 가능
    }
})


: 로컬변수 counter에 무명객체 object가 접근해서 값을 설정할 수 있습니다