Kotlin의 Companion object: 개념, 구문 및 활용

저자: IT Sectr 게시일: 2026-06-21 읽는 시간: 9 분

companion object는 Java의 static 키워드를 대체하여 클래스의 정적 멤버를 선언하는 Kotlin 언어의 메커니즘입니다. Kotlin에는 내장 static이 없습니다 — 대신 클래스 내부에 companion 수정자가 있는 object 선언을 사용합니다. JetBrains, 2025에 따르면, companion object를 사용하면 클래스 이름을 통해 메서드와 속성을 호출할 수 있어 Java 생태계의 정적 멤버와 완전히 동일합니다.

핵심 요점

  • companion object는 클래스 내부에 companion 키워드가 있는 object 선언으로, Java static을 대체합니다
  • 정적 멤버는 인스턴스를 생성하지 않고 클래스 이름을 통해 접근 가능: ClassName.method()
  • 이름이 있는 companion object는 더 읽기 쉬운 코드를 위해 객체에 이름을 부여할 수 있습니다
  • 인터페이스도 Kotlin에서 메서드 구현이 있는 companion object를 포함할 수 있습니다
  • @JvmStatic — companion object 메서드를 Java에서 실제 static 메서드로 내보내는 어노테이션

Kotlin에서 companion object란?

companion object는 Kotlin에서 companion 키워드로 표시된 특별한 종류의 object 선언입니다. 인스턴스가 아닌 클래스에 속하는 멤버 — 즉 Java 용어로 정적 멤버를 선언할 수 있습니다. static이 개별 필드와 메서드에 대한 수정자인 Java와 달리, Kotlin에서는 정적 멤버가 단일 컴패니언 객체 내부에 그룹화됩니다.

Kotlin의 모든 클래스는 정확히 하나의 companion object를 포함할 수 있습니다. 이 객체의 멤버는 인스턴스를 생성하지 않고 클래스 이름을 통해 접근 가능합니다: MyClass.method(). 바이트코드 관점에서 companion object는 별도의 내부 클래스로 컴파일되며, @JvmStatic 어노테이션을 사용하면 해당 메서드는 외부 클래스의 정적 메서드가 됩니다.

Google I/O 2024에 따르면, companion object는 Kotlin으로 작성된 최신 Android 애플리케이션에서 팩토리 메서드, 상수 및 유틸리티 함수의 주요 패턴이 되었습니다. 개발자들은 패키지 수준 함수가 있는 유틸리티 클래스보다 이것을 선택하는데, companion object가 소유 클래스와의 논리적 연결을 유지하기 때문입니다.

정적 컨텍스트 — 상수, 팩토리, 논리적으로 클래스에 속하지만 인스턴스가 필요하지 않은 헬퍼 메서드를 그룹화하려면 companion object를 사용하세요.

Companion object 구문: 선언 및 접근

companion object의 기본 구문은 간단합니다: companion 키워드를 클래스 내부의 object 선언 앞에 배치합니다. 이름이 지정되지 않으면 객체는 Companion이라는 이름을 가지며, 명시적 또는 암시적으로 접근할 수 있습니다.

kotlin
class User {
    companion object {
        const val TABLE_NAME = "users"

        fun create(name: String): User {
            return User(name)
        }
    }

    val name: String
    constructor(name: String) { this.name = name }
}

// Access via class name
val table = User.TABLE_NAME
val user = User.create("Alice")

위 예제에서 TABLE_NAME과 create()는 User.TABLE_NAME과 User.create()를 통해 접근 가능합니다. 기본형 상수에 대한 const 수정자에 주목하세요 — 이는 컴파일 시간에 값 인라인을 보장하며, 기본형 및 String에 대한 Java static final과 유사합니다.

Companion을 통한 암시적 및 명시적 접근

companion object에 이름이 없는 경우 자동 이름 Companion: User.Companion.TABLE_NAME을 통해 접근할 수 있습니다. 실제로는 거의 필요하지 않지만, Java 코드에서 호출하거나 리플렉션을 사용할 때 유용합니다.

kotlin
// Both calls are equivalent
User.create("Bob")
User.Companion.create("Bob")

// Getting reference to companion object
val companion: User.Companion = User

이름이 있는 companion object 및 특징

companion object에는 이름을 지정할 수 있어 코드 가독성이 향상되고 의미 있는 이름으로 접근할 수 있습니다. 팩토리로 사용되는 companion object에서는 Factory가 가장 일반적인 이름입니다.

kotlin
class HttpResponse {
    companion object Factory {
        fun ok(body: String): HttpResponse = HttpResponse(200, body)
        fun notFound(): HttpResponse = HttpResponse(404, "Not Found")
        fun serverError(): HttpResponse = HttpResponse(500, "Internal Error")
    }
}

// Access via Factory name
val response = HttpResponse.Factory.ok("data")

이름이 있는 companion object는 정적 멤버의 목적을 문서화하는 데 도움이 됩니다. 리팩토링에서 이름은 코드를 자체 문서화하게 만듭니다 — 개발자는 Factory가 HttpResponse 인스턴스를 생성한다는 것을 즉시 알 수 있습니다.

제한 사항: 클래스당 정확히 하나의 companion object

클래스는 하나의 companion object만 포함할 수 있습니다. 이는 그룹화 없이 원하는 수의 정적 필드와 메서드를 선언할 수 있는 Java와의 근본적인 차이점입니다. 정적 멤버의 여러 논리적 그룹이 필요한 경우, companion 없이 중첩된 object 선언을 사용하세요.

공식 Kotlin 문서(Kotlin Docs, 2025)에 따르면, 하나의 companion object 제한은 언어 설계에 의해 동기부여되었습니다: 정적 멤버는 클래스와 긴밀하게 결합되어야 하며, 하나의 컴패니언 객체가 이 연결에 대한 명확한 경계를 제공합니다. 정적 멤버가 너무 많으면 클래스를 리팩토링해야 한다는 신호입니다.

인터페이스의 Companion object

Kotlin에서는 인터페이스도 companion object를 포함할 수 있습니다. 이를 통해 Java에서는 불가능한, 인터페이스 내부에 직접 정적 메서드와 상수를 정의할 수 있습니다. 이 접근 방식은 인터페이스와 관련된 상수나 팩토리 메서드를 선언하는 데 자주 사용됩니다.

kotlin
interface ApiService {
    companion object {
        const val BASE_URL = "https://api.example.com"
        const val TIMEOUT_MS = 5000

        fun create(client: OkHttpClient): ApiService =
            Retrofit.Builder()
                .baseUrl(BASE_URL)
                .client(client)
                .build()
                .create(ApiService::class.java)
    }
}

이 예제에서 BASE_URL, TIMEOUT_MS 및 create()는 ApiService 인터페이스에 속하며, 그 구현체가 아닙니다. 이는 편리합니다: 모든 상수와 API 서비스 인스턴스 생성을 위한 팩토리 로직이 한 곳 — 인터페이스 자체 내부에 있습니다.

인터페이스 구현체는 companion object의 멤버를 상속하지 않습니다 — 인터페이스 이름: ApiService.BASE_URL을 통해서만 호출됩니다.

@JvmStatic 및 @JvmField: Java 호환성

Java 코드에서 companion object 메서드를 호출할 때, 기본적으로 외부 클래스의 정적 메서드가 아닌 중첩된 Companion 클래스의 메서드로 접근 가능합니다. 이를 Java의 실제 정적 메서드와 필드로 내보내려면 메서드에는 @JvmStatic 어노테이션을, 필드에는 @JvmField 어노테이션을 사용하세요.

어노테이션적용Java에서의 결과
@JvmStaticCompanion object 메서드정적 메서드: ClassName.method()
@JvmFieldCompanion object 필드정적 필드: ClassName.field
const기본형 및 String인라인 상수: 컴파일러가 값을 대체
어노테이션 없음기본 메서드/필드Companion.method() / Companion.field
kotlin
class MathUtils {
    companion object {
        const val PI = 3.14159

        @JvmStatic
        fun square(x: Int): Int = x * x

        @JvmField
        val TAG: String = "MathUtils"
    }
}

// Called in Java as MathUtils.square(5), MathUtils.TAG

Java 코드에서 접근 가능해야 하는 모든 공개 companion object 메서드에는 @JvmStatic을 사용하는 것이 좋습니다. const는 기본형과 String에만 적용됩니다 — 다른 형식에는 @JvmField를 사용하세요.

Companion object 사용의 실제 예제

실제 프로젝트에서 companion object는 여러 표준 시나리오에 사용됩니다. 팩토리 메서드가 가장 일반적인 패턴입니다: 다른 시그니처를 가진 여러 생성자 대신, companion object에서 이름이 있는 팩토리 메서드를 사용합니다.

kotlin
sealed class NetworkResult<out T> {
    data class Success<out T>(val data: T) : NetworkResult<T>()
    data class Error(val message: String) : NetworkResult<Nothing>()

    companion object {
        fun loading<T>(): NetworkResult<T> =
            Loading()
    }
}

private class Loading<T> : NetworkResult<T>()

NetworkResult의 companion object는 올바른 제네릭 타입으로 Loading 인스턴스를 생성하는 팩토리 메서드 loading()을 제공합니다. 이 메서드가 없으면 Loading()을 통해 직접 인스턴스를 생성해야 하며, 이는 내부 구현을 노출시킵니다.

상수 및 로깅 태그

companion object는 종종 클래스별 상수를 저장하는 데 사용됩니다. const val로 선언된 상수는 컴파일 시간에 인라인되어 런타임 오버헤드가 없습니다.

kotlin
class UserRepository {
    companion object {
        private const val TAG = "UserRepository"
        private const val CACHE_SIZE = 100
        private const val DEFAULT_PAGE_SIZE = 20
    }
}

자주 묻는 질문

Kotlin에서 일반 object와 companion object의 차이점은 무엇인가요?

일반 object는 클래스와 독립적으로 존재하는 완전한 싱글톤입니다. companion object는 클래스 내부의 object이며, 그 멤버는 외부 클래스 이름을 통해 호출됩니다. 바이트코드에서 companion object는 정적 중첩 클래스가 되고, 일반 object는 자율적인 싱글톤 클래스가 됩니다.

companion object를 상속할 수 있나요?

아니요, companion object는 하위 클래스에 상속되지 않습니다. 클래스 B가 클래스 A를 확장하는 경우, B.method()는 클래스 A의 companion object에서 메서드를 호출하지 않습니다 — A.method()를 통해 호출해야 합니다. 이는 Java의 정적 메서드 동작과 일치합니다.

Java 코드에서 companion object를 어떻게 호출하나요?

기본적으로 — ClassName.Companion.method()를 통해. 정적 메서드로 호출하려면 companion object 메서드에 @JvmStatic을 추가하세요. 필드의 경우 @JvmField를 사용하거나 기본형에 대해 const val로 선언하세요.

Kotlin에는 왜 static 키워드가 없나요?

Kotlin 개발자들은 객체와의 일관된 작업을 위해 static을 포기하고 companion object를 채택하기로 결정했습니다. Java의 static은 정적 메서드가 인스턴스에 바인딩되지 않기 때문에 OOP 원칙을 위반합니다. companion object는 일급 객체로, 전달하고, 확장 함수로 확장하고, 인터페이스를 구현할 수 있습니다.

companion object가 성능에 영향을 미치나요?

기본형 및 String에 const val을 사용할 때 — 오버헤드가 없으며 값이 바이트코드에 인라인됩니다. 메서드의 경우, 메서드가 inline이 아닌 한 오버헤드가 없습니다. 대부분의 경우 companion object는 성능에 측정 가능한 영향을 미치지 않습니다. Java에서 호출되는 공개 API에만 @JvmStatic을 사용하세요.

요약

  • companion object는 Java static을 대체하는 Kotlin의 클래스 정적 멤버 선언 메커니즘
  • 클래스당 하나의 companion object — 모든 정적 요소를 단일 컴패니언 객체에 그룹화
  • 접근은 클래스 이름을 통해: ClassName.method() 또는 ClassName.Companion.method()
  • @JvmStatic 및 @JvmField가 멤버를 Java의 실제 정적 메서드 및 필드로 내보냄
  • const val은 기본형 및 String용 — 오버헤드 없이 컴파일 시간에 값 인라인
  • 인터페이스도 상수 및 팩토리 메서드를 위해 companion object 지원
  • 이름이 있는 companion object가 코드 가독성을 향상시키고 정적 멤버의 목적을 자체 문서화

턴키 방식의 모바일 애플리케이션을 개발해 드립니다

IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.

프로젝트 논의

더 읽어보기