티스토리 뷰

728x90

 

companion object
class WithCompanion {
    ...
    companion object {
        val i = 3
        fun f() = i * 3
    }
}

fun main() {
    println(WithCompanion.i)
    println(WithCompanion.Companion.i)
    
    WithCompanion.f()
    WithCompanion.Companion.f()
}
  • 클래스의 일부만 싱글톤으로 구현하고 싶을 때 사용한다.
  • 클래스명.Companion 로 companion object에 접근할 수 있고 Companion은 생략 가능하다.
  • 클래스명으로 접근할 수 있기 때문에 클래스 내에 companion object는 단 하나만 생성 가능하다. (일반 object는 클래스 내에 여러개 구현 가능)

 

class WithCompanion {
    fun increment() = ++n
    
    companion object {
        private var n = 0
    }
}

fun main() {
    val a = WithCompanion()
    val b = WithCompanion()
    
    a.increment() // 1
    b.increment() // 2
    a.increment() // 3
}
  • 외부 클래스에서 companion object의 프로퍼티, 함수에 바로 접근할 수 있다.
  • 반대로 companion object는 외부 클래스의 프로퍼티, 함수에 접근할 수 없다.
  • companion object 안에서 필드를 생성하면, 필드는 메모리상에 단 하나만 존재하게 된다.
  • 외부 클래스 모든 인스턴스가 이 필드를 공유한다.

 
 

companion object에 이름을 지정하기
class WithCompanion {
    ...
    companion object Named {
        val i = 3
        fun f() = i * 3
    }
}

fun main() {
    println(WithCompanion.i)
    println(WithCompanion.Named.i)
    println(WithCompanion.Companion.i) // compile error: Unresolved reference
}
  • 명확성을 위해 companion object에 이름을 붙여줄 수 있다.
  • 이름을 부여하더라도 클래스명만을 사용해서 companion object의 원소에 접근할 수 있다.
  • 코틀린이 부여해준 Companion이라는 이름을 Named로 지정해준 것이기 때문에 Companion이라는 이름은 사용할 수 없다.

 
 

companion object의 확장 함수
class WithCompanion {
    ...    
    companion object {
        val i = 3
        fun f() = i * 3
    }
}

fun WithCompanion.instanceFunction() = 3

fun WithCompanion.Companion.companionFunction() = f()

fun main() {
    WithCompanion.companionFunction()
    WithCompanion().instanceFunction()
}
  • Companion에 대한 확장 함수를 사용할 수 있는데, 이 경우 확장 함수의 receiver로 클래스명.Companion을 꼭 명시해야 한다.
  • 클래스명만 사용하는 경우 companion object가 아닌, 인스턴스에 대한 확장 함수가 생성된다.

 
 

companion object가 메모리에 올라가는 시점
class WithCompanion { 
    init {
        println("Outer Class Constructor")
    }
    
    companion object {
        init {
            println("Companion Constructor")
        }
    }
}

fun main() {
    println("Before")
    WithCompanion()
    WithCompanion()
    WithCompanion()
}
출력 결과
Before
Companion Constructor
Outer Class Constructor
Outer Class Constructor
Outer Class Constructor
  • companion object는 외부 클래스가 메모리에 적재될 때 같이 생성된다.
  • 위 출력문을 통해 companion object는 외부 클래스보다 더 먼저, 단 한번만 생성됨을 알 수있다.

 

 

companion object도 인스턴스다
interface CompanionInterface {
    fun i(): Int
}

class WithCompanion { 
    ...
    companion object : CompanionInterface {
        override fun i() = 3
    }
}

fun main() {
    WithCompanion.i()
}
  • object와 마찬가지로, companion object 자체도 인스턴스다.
  • 즉, 인터페이스를 구현하거나 클래스를 상속받을 수 있다.
  • 일반 객체처럼 다른 곳에 전달될 수 있다.
  • init 블록을 사용할 수 있다.

 
 

object vs companion object
  • object
    • 클래스 전체가 하나의 싱글톤으로 선언된다.
    • 외부에서 object에 접근할 때까지 초기화가 지연된다.
    • 클래스 내에 여러개를 생성할 수 있다.
  • companion object
    • 클래스의 일부분이 싱글톤으로 선언된다.
    • object가 속한 클래스가 사용될 때 초기화된다.
    • 클래스 내에 하나만 생성할 수 있다.

 

자바의 static vs 코틀린의 companion object
  • 자바의 클래스 내에 필드나 메서드를 static으로 선언하면, 클래스의 모든 인스턴스들이 static 멤버를 공유한다.
  • 자바의 static과 코틀린의 companion object 모두 클래스명만 가지고 필드나 메서드에 접근할 수 있다.

 
companion object 자체는 static으로 생성되지만, (const를 붙이지 않은) 일반 속성과 함수는 static이 아니다.
그럼 companion object 내의 일반 속성과 함수를 자바에서 접근하려고 하면 어떻게 될까?

class WithCompanion {
    ...
    companion object {
        const val constValue = 3
        val notConstValue = 3
        
        fun companionFunction() = 3
    }
}
public class Main {
	public static void main(String[] args) {
        // 1
        int constValue = WithCompanion.constValue // o
        
        // 2
        int notConstValue = WithCompanion.notConstValue // error
        int notConstValue = WithCompanion.INSTANCE.getNotConstValue() // o
        
        // 3
        WithCompanion.companionFunction() // error
        WithCompanion.INSTANCE.companionFunction() // o
    }
}
  • 1 - const val 로 생성한 속성은 코틀린 코드와 동일하게 클래스명으로 속성에 접근할 수 있다.
  • 2 - const 키워드를 사용하지 않은 속성은 자바의 INSTANCE에 접근해 getter를 호출해야한다.
  • 3 - 함수 또한 INSTANCE에 접근해 호출한다.

요약하자면, const 키워드를 사용한 속성만 static이기 때문에 코틀린과 동일하게 클래스명으로 바로 접근이 가능하다.
일반 속성과 함수는 INSTANCE를 꼭 붙여야 한다.
 
이 때 코틀린에서 자바와의 상호 운용성을 위해 제공하는 어노테이션을 활용하면 된다.

class WithCompanion {
    ...
    companion object {
        @JvmField
        val notConstValue = 1..3
        
        @JvmStatic
        fun companionFunction() = 3
    }
}
public class Main {
	public static void main(String[] args) {
        // 1
        IntRange constValue = WithCompanion.notConstValue // o
        
        // 2
        int companionFunctionValue = WithCompanion.companionFunction()
    }
}
  • 1 - const 키워드를 사용하지 않은 속성도 클래스명으로 바로 접근할 수 있다.
  • 2 - 함수를 클래스명으로 바로 접근함을 알 수 있다.

@JvmField는 속성을 static 속성으로 변경해주고, @JvmStatic은 함수를 static 함수로 변경해준다.
 
 

companion object는 언제 사용할까?
  • 클래스의 인스턴스를 생성하지 않고 클래스를 접근하고자 할 때 사용한다.
  • 클래스의 private 생성자에 접근할 수 있기 때문에, 팩토리 패턴을 구현하고자 할 때 사용한다.

 
 
 


참고 자료

https://kotlinlang.org/docs/object-declarations.html#object-declarations-overview
https://coding-factory.tistory.com/524 
길벗 [아토믹 코틀린], [코틀린 완벽 가이드]
 

728x90
250x250
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2024/10   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31
글 보관함