companion objectは、Javaのstaticキーワードに代わってクラスの静的メンバーを宣言するKotlin言語のメカニズムです。Kotlinには組み込みのstaticがありません — 代わりに、クラス内でcompanion修飾子を付けたobject宣言を使用します。JetBrains, 2025によると、companion objectを使用するとクラス名を通じてメソッドとプロパティを呼び出すことができ、Javaエコシステムにおける静的メンバーの完全な等価物となります。
重要なポイント
companion objectは、companionキーワードでマークされたKotlinのobject宣言の特別な種類です。これにより、インスタンスではなくクラスに属するメンバー — つまりJava用語での静的メンバーを宣言できます。staticが個々のフィールドとメソッドの修飾子であるJavaとは異なり、Kotlinでは静的メンバーは単一のコンパニオンオブジェクト内にグループ化されます。
Kotlinの任意のクラスは正確に1つのcompanion objectを含めることができます。このオブジェクトのメンバーは、インスタンスを作成せずにクラス名を通じてアクセス可能です: MyClass.method()。バイトコードの観点では、companion objectは別の内部クラスにコンパイルされ、@JvmStaticアノテーションを使用するとそのメソッドは外部クラスの静的メソッドになります。
Google I/O 2024によると、companion objectはKotlinで書かれた最新のAndroidアプリケーションにおけるファクトリメソッド、定数、ユーティリティ関数の主要なパターンになっています。開発者はパッケージレベル関数を持つユーティリティクラスよりもこれを選択します。companion objectは所有クラスとの論理的な接続を維持するからです。
staticコンテキストをグループ化するためにcompanion objectを使用してください — 論理的にはクラスに属するがインスタンスを必要としない定数、ファクトリ、ヘルパーメソッドなど。
companion objectの基本構文はシンプルです: companionキーワードをクラス内のobject宣言の前に置きます。名前が指定されない場合、オブジェクトはCompanionという名前になり、明示的または暗黙的にアクセスできます。
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 objectに名前がない場合、自動名Companion: User.Companion.TABLE_NAMEを通じてアクセスできます。実際にはこれはほとんど必要ありませんが、Javaコードから呼び出す場合やリフレクションを使用する場合に役立ちます。
// 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ではFactoryが最も一般的な名前です。
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のインスタンスを作成することを即座に理解できます。
クラスは1つのcompanion objectのみを含めることができます。これは、グループ化なしで任意の数の静的フィールドとメソッドを宣言できるJavaとの基本的な違いです。静的メンバーの複数の論理グループが必要な場合は、companionなしのネストされたobject宣言を使用してください。
公式Kotlinドキュメント(Kotlin Docs, 2025)によると、1つのcompanion objectの制限は言語設計に動機づけられています: 静的メンバーはクラスと緊密に結合されるべきであり、1つのコンパニオンオブジェクトがこの接続の明確な境界を提供します。静的メンバーが多すぎる場合は、クラスのリファクタリングのシグナルです。
Kotlinでは、インターフェースもcompanion objectを含めることができます。これにより、Javaでは不可能な、インターフェース内に直接静的メソッドと定数を定義できます。このアプローチは、インターフェースに関連する定数やファクトリメソッドの宣言によく使用されます。
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サービスインスタンスを作成するためのすべての定数とファクトリロジックが1つの場所 — インターフェース自体の内部にあります。
インターフェースの実装はcompanion objectのメンバーを継承しません — これらはインターフェース名: ApiService.BASE_URLを通じてのみ呼び出されます。
Javaコードからcompanion objectのメソッドを呼び出す場合、デフォルトでは外部クラスの静的メソッドとしてではなく、ネストされたCompanionクラスのメソッドとしてアクセス可能です。それらをJavaの実際の静的メソッドとフィールドとしてエクスポートするには、メソッドに@JvmStaticアノテーションを、フィールドに@JvmFieldアノテーションを使用します。
| アノテーション | 適用 | Javaでの結果 |
|---|---|---|
| @JvmStatic | Companion objectのメソッド | 静的メソッド: ClassName.method() |
| @JvmField | Companion objectのフィールド | 静的フィールド: ClassName.field |
| const | プリミティブとString | インライン定数: コンパイラが値を置換 |
| アノテーションなし | デフォルトのメソッド/フィールド | Companion.method() / Companion.field |
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で名前付きファクトリメソッドを使用します。
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で宣言された定数はコンパイル時にインライン化され、実行時のオーバーヘッドがゼロになります。
class UserRepository {
companion object {
private const val TAG = "UserRepository"
private const val CACHE_SIZE = 100
private const val DEFAULT_PAGE_SIZE = 20
}
}
よくある質問
通常のobjectは、クラスから独立して存在する完全なシングルトンです。companion objectはクラス内のobjectであり、そのメンバーは外部クラス名を通じて呼び出されます。バイトコードでは、companion objectは静的ネストクラスになり、通常のobjectは自律的なシングルトンクラスになります。
いいえ、companion objectはサブクラスに継承されません。クラスBがクラスAを拡張する場合、B.method()はクラスAのcompanion objectのメソッドを呼び出しません — A.method()を通じて呼び出す必要があります。これはJavaの静的メソッドの動作と一致します。
デフォルトでは — ClassName.Companion.method()を通じて。静的メソッドとして呼び出すには、companion objectのメソッドに@JvmStaticを追加します。フィールドには@JvmFieldを使用するか、プリミティブの場合はconst valで宣言します。
Kotlinの開発者は、オブジェクトとの統一的な作業のためにstaticを廃止しcompanion objectを採用することを決定しました。Javaのstaticは、静的メソッドがインスタンスに結びついていないため、OOPの原則に違反します。companion objectは第一級オブジェクトであり、受け渡し、拡張関数による拡張、インターフェースの実装が可能です。
プリミティブとStringにconst valを使用する場合 — オーバーヘッドはゼロで、値はバイトコードにインライン化されます。メソッドの場合、メソッドがinlineでない限りオーバーヘッドはありません。ほとんどの場合、companion objectはパフォーマンスに測定可能な影響を与えません。@JvmStaticはJavaから呼び出されるパブリックAPIにのみ使用してください。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。