Kotlin: Kotlinのオブジェクト式とコンパニオンオブジェクト
最終更新:2026-08-26
Kotlinのobjectキーワードは、シングルトンの宣言、コンパニオンオブジェクト、および匿名オブジェクトという3つの形式を統合したものです。 Charlieは、Javaのstaticファクトリメソッドの代わりにcompanion objectを使用し、シングルトンユーティリティクラスの代わりにobject宣言を使用しています。
1. 学習内容
object宣言:Kotlinのシングルトンパターンcompanion object: ファクトリメソッドと静的メンバーの置換- オブジェクト式:匿名実装
- コンパニオンオブジェクトの拡張:クラスレベルの拡張関数
- Charlieの実演:
Orderファクトリ +OrderValidatorシングルトン
2. 本物の建築家の物語
(1) 課題:Javaのシングルトンと静的メソッドの乱立
CharlieのJavaプロジェクトには、10以上のシングルトンクラス(getInstance())と膨大な数の静的ユーティリティメソッド(static)があり、テストも拡張も困難でした。
(2) Kotlin オブジェクトによる解決策
KOTLIN
// Java: verbose singleton
public class OrderValidator {
private static final OrderValidator INSTANCE = new OrderValidator();
private OrderValidator() {}
public static OrderValidator getInstance() { return INSTANCE; }
}
// Kotlin: 1-line singleton
object OrderValidator {
fun validate(order: Order) = require(order.total >= 0)
}
Kotlinのたった1行のコードで、Javaの5行にわたるシングルトンの定型コードを置き換えることができ、しかも当然ながらスレッドセーフです。
3. オブジェクトの宣言(シングルトン)
(1) 基本的な構文
KOTLIN
// Singleton: only one instance, thread-safe initialization
object OrderValidator {
fun validate(order: Order): Boolean {
return order.total >= 0 && order.id.startsWith("ORD-")
}
}
// Usage: access directly by object name
OrderValidator.validate(order) // No getInstance() needed
(2) 状態を持つシングルトン
KOTLIN
object OrderCounter {
private var count = 0L
fun increment() { count++ }
fun getCount() = count
fun reset() { count = 0 }
}
OrderCounter.increment()
println(OrderCounter.getCount()) // 1
(3) オブジェクトとJavaのシングルトン
| 次元 | Javaのシングルトン | Kotlinのオブジェクト |
|---|---|---|
| コード行数 | 5行以上 | 1行 |
| スレッドセーフ | 手動で確保する必要がある | 自然にセーフ |
| シリアライズ | 特別な処理が必要 | 自然に正しい |
| テスト容易性 | 難しい | 同様に難しい(グローバル状態) |
| 代替案 | 依存性注入 | 依存性注入(推奨) |
4. コンパニオンオブジェクト
(1) 基本的な構文
KOTLIN
class Order(val id: String, val total: Double, val status: String) {
companion object {
// Factory methods
fun create(id: String, total: Double): Order =
Order(id, total, "PENDING")
fun fromCsv(line: String): Order {
val parts = line.split(",")
return Order(parts[0], parts[1].toDouble(), parts[2])
}
// Constants
const val DEFAULT_STATUS = "PENDING"
const val MAX_TOTAL = 1_000_000.0
}
}
// Usage: like static in Java
val order = Order.create("ORD-001", 299.99)
val csvOrder = Order.fromCsv("ORD-002,1500.00,CONFIRMED")
println(Order.DEFAULT_STATUS)
(2) 名前付きコンパニオンオブジェクト
KOTLIN
class Order(val id: String, val total: Double) {
companion object Factory {
fun create(id: String, total: Double) = Order(id, total)
}
}
// Can use either name
Order.create("ORD-001", 299.99)
Order.Factory.create("ORD-001", 299.99)
(3) コンパニオンオブジェクトによるインターフェースの実装
KOTLIN
interface JsonFactory<T> {
fun fromJson(json: String): T
}
class Order(val id: String, val total: Double) {
companion object : JsonFactory<Order> {
override fun fromJson(json: String): Order {
// Parse json and create Order
return Order("ORD-from-json", 0.0)
}
}
}
// Polymorphic usage
val factory: JsonFactory<Order> = Order
val order = factory.fromJson("{\"id\":\"ORD-001\"}")
(4) コンパニオンオブジェクトとJavaのstatic
| 次元 | Javaのstatic | Kotlinのコンパニオンオブジェクト |
|---|---|---|
| ネイチャー | クラスレベルのメンバー | オブジェクト(コンパニオン・シングルトン) |
| インターフェースの実装 | できない | できる |
| 拡張関数 | できない | できる |
| 名称 | 名称なし | 名称を付けられる |
| 実行時の存在 | 存在しない | 存在する(シングルトンオブジェクト) |
5. オブジェクト式(匿名オブジェクト)
(1) 基本的な構文
KOTLIN
// Anonymous object: like Java anonymous inner class
val handler = object : OrderHandler {
override fun handle(order: Order) {
println("Handling ${order.id}")
}
}
// Anonymous object implementing multiple interfaces
val multiHandler = object : OrderHandler, Loggable {
override fun handle(order: Order) { println("Handle: ${order.id}") }
override fun log(message: String) { println("[LOG] $message") }
}
(2) 外部変数を保持する匿名オブジェクト
KOTLIN
fun createHandler(prefix: String): OrderHandler {
var count = 0 // Captured by the anonymous object
return object : OrderHandler {
override fun handle(order: Order) {
count++
println("$prefix [#$count] ${order.id}")
}
}
}
(3) 3つのオブジェクト・フォームの比較
| フォーム | 構文 | ライフサイクル | ユースケース |
|---|---|---|---|
object 宣言 |
object Name { } |
プロセスレベルのシングルトン | グローバルユーティリティ/設定 |
companion object |
class X { companion object { } } |
クラスレベルのシングルトン | ファクトリメソッド/定数 |
| オブジェクト式 | object : Interface { } |
一時インスタンス | 単発の実装 |
6. 実用的なユースケースの比較
| シナリオ | 推奨形式 | 例 |
|---|---|---|
| グローバル設定 | object の宣言 |
object AppConfig |
| ファクトリメソッド | companion object |
Order.create(...) |
| 単発のコールバック | オブジェクト式 | object : OrderHandler { } |
| 定数コレクション | companion object + const |
const val MAX = 1_000_000 |
| ユーティリティ関数 | object 宣言またはトップレベル関数 |
object OrderUtils |
| マルチインターフェースの実装 | オブジェクト式 | object : A, B { } |
7. コンパニオンオブジェクトの拡張関数
KOTLIN
class Order(val id: String, val total: Double) {
companion object // Empty - exists as extension target
}
// Extension on companion object
fun Order.Companion.fromCsv(line: String): Order {
val parts = line.split(",")
return Order(parts[0], parts[1].toDouble())
}
// Usage
val order = Order.fromCsv("ORD-001,299.99")
💡 ヒント: コンパニオンオブジェクトの拡張関数を使えば、サードパーティのライブラリは、クラスのソースコードを変更することなく、そのクラスに「静的メソッド」を追加することができます。
7. オブジェクトのフォーム選択フロー
flowchart TD
A[Need object?] --> B{Strongly tied to a class?}
B -->|Yes| C{Need factory/constants?}
B -->|No| D{Need one-time instance?}
C -->|Yes| E[companion object]
C -->|No| F{Need global singleton?}
D -->|Yes| G[Object Expression<br/>object : Interface]
D -->|No| H[Regular class instance]
F -->|Yes| I[object Declaration<br/>Singleton]
F -->|No| H
8. 完全な例
▶ サンプル:オブジェクトとファクトリの実装
KOTLIN
// ============================================
// OrderProcessor - Object Patterns
// Feature: Singleton, Companion Factory, Anonymous Handler
// ============================================
data class Order(val id: String, val total: Double, var status: String, val customer: String) {
companion object {
const val DEFAULT_STATUS = "PENDING"
const val MAX_TOTAL = 1_000_000.0
fun create(id: String, total: Double, customer: String): Order {
require(total >= 0) { "Total must be non-negative" }
require(total <= MAX_TOTAL) { "Total exceeds maximum" }
return Order(id, total, DEFAULT_STATUS, customer)
}
fun bulkCreate(vararg ids: String, total: Double, customer: String): List<Order> {
return ids.map { create(it, total, customer) }
}
}
}
// Singleton validator
object OrderValidator {
private val rules = mutableListOf<(Order) -> Boolean>()
fun addRule(rule: (Order) -> Boolean) { rules.add(rule) }
fun validate(order: Order): List<String> {
val errors = mutableListOf<String>()
if (!order.id.startsWith("ORD-")) errors.add("Invalid ID format")
if (order.total < 0) errors.add("Negative total")
rules.forEach { rule ->
if (!rule(order)) errors.add("Custom rule failed")
}
return errors
}
}
// Singleton counter
object OrderMetrics {
private var totalProcessed = 0L
private var totalRevenue = 0.0
@Synchronized fun record(order: Order) {
totalProcessed++
totalRevenue += order.total
}
fun report() = "Processed: $totalProcessed orders, Revenue: \$$totalRevenue USD"
}
interface OrderHandler {
fun handle(order: Order): String
}
fun main() {
// Companion factory
println("=== Factory Creation ===")
val order1 = Order.create("ORD-001", 299.99, "Alice")
val order2 = Order.create("ORD-002", 15_000.00, "Bob")
val bulkOrders = Order.bulkCreate("ORD-010", "ORD-011", "ORD-012", total = 500.0, customer = "Charlie")
println(order1)
println(order2)
bulkOrders.forEach { println(it) }
// Singleton validator
println("\n=== Validation ===")
OrderValidator.addRule { it.total > 0 }
val validOrder = Order("ORD-100", 299.99, "PENDING", "Alice")
val invalidOrder = Order("BAD-001", -50.0, "PENDING", "Bob")
println("Valid: ${OrderValidator.validate(validOrder)}")
println("Invalid: ${OrderValidator.validate(invalidOrder)}")
// Singleton metrics
println("\n=== Metrics ===")
listOf(order1, order2).forEach { OrderMetrics.record(it) }
println(OrderMetrics.report())
// Anonymous handler
println("\n=== Anonymous Handler ===")
val shippingHandler = object : OrderHandler {
private var shipped = 0
override fun handle(order: Order): String {
shipped++
order.status = "SHIPPED"
return "Shipped ${order.id} (Total shipped: $shipped)"
}
}
println(shippingHandler.handle(order1))
println(shippingHandler.handle(order2))
}
出力:
TEXT
📖 参照専用
=== Factory Creation ===
Order(id=ORD-001, total=299.99, status=PENDING, customer=Alice)
Order(id=ORD-002, total=15000.0, status=PENDING, customer=Bob)
Order(id=ORD-010, total=500.0, status=PENDING, customer=Charlie)
Order(id=ORD-011, total=500.0, status=PENDING, customer=Charlie)
Order(id=ORD-012, total=500.0, status=PENDING, customer=Charlie)
=== Validation ===
Valid: []
Invalid: [Invalid ID format, Negative total, Custom rule failed]
=== Metrics ===
Processed: 2 orders, Revenue: $15299.99 USD
=== Anonymous Handler ===
Shipped ORD-001 (Total shipped: 1)
Shipped ORD-002 (Total shipped: 2)
❓ よくある質問
Q オブジェクトシングルトンは依存性注入を受け入れることができますか?
A 技術的には可能です(
varのプロパティを介して)が、推奨されません。シングルトンのグローバル状態があると、テストが困難になります。本番環境向けのコードでは、オブジェクトシングルトンよりもDIフレームワーク(Koin/Dagger)を使用することをお勧めします。Q コンパニオンオブジェクトのメンバは、実際には静的メソッドなのでしょうか?
A JVM上では、コンパニオンオブジェクト内で
@JvmStaticというアノテーションが付けられたメソッドは、真の静的メソッドとしてコンパイルされます。アノテーションがない場合、それらはコンパニオンオブジェクト上のインスタンスメソッドのままとなります。Q 匿名オブジェクトは外部変数にアクセスできますか?
A はい。Javaの匿名内部クラスとは異なり、Kotlinの匿名オブジェクトは(クロージャと同様に)外部変数にアクセスし、変更することができます。
Q コンパニオンオブジェクトとオブジェクト宣言のどちらを選べばよいですか?
A クラスと密接に結びついているもの(ファクトリメソッド、定数など)には
companion object を使用してください。どのクラスにも依存しないもの(グローバルユーティリティ、設定など)には object 宣言を使用してください。Q コンパニオンオブジェクトにおいて、
const val と通常の val の違いは何ですか?A
const val はコンパイル時定数(呼び出し箇所でインライン展開される)であり、プリミティブ型および String にのみ適用されます。通常の val は実行時に初期化されます。Q コンパニオンオブジェクトにスーパタイプは設定できますか?
A はい。コンパニオンオブジェクトはインターフェースを実装できるため、ファクトリパターンをより洗練されたものにすることができます:
companion object : JsonFactory<Order>。📖 まとめ
objectの宣言により、スレッドセーフなシングルトンが作成され、Java の 5 行以上に及ぶシングルトンの定型コードが不要になりますcompanion objectは Javastaticに代わるもので、より強力な機能(インターフェース、拡張関数、命名規則)を備えています。- オブジェクト式
object : Interface { }は、Java の匿名内部クラスの代わりとなる - コンパニオンオブジェクトの拡張関数を使えば、ソースコードを変更することなく「静的メソッド」を追加できます
- シングルトンはテストが難しいため、本番環境ではオブジェクトのシングルトンよりも依存性注入を採用することを推奨します
- コンパイル時定数には
const val、実行時初期化にはval
📝 練習問題
- 初心者 (⭐):
OrderCounterシングルトンを作成し、increment()およびgetCount()メソッドを実装してください。ヒント:object OrderCounter { ... } - 中級 (⭐⭐):
Orderクラスにcompanion objectを追加し、CSV の行を解析して Order を作成するfromCsvファクトリメソッドを実装してください。ヒント:companion object { fun fromCsv(line: String): Order { ... } } - 課題 (⭐⭐⭐): コンパニオンオブジェクトのインターフェース
JsonFactory<Order>およびコンパニオンオブジェクトの拡張Order.Companion.fromXml()を実装してください。ヒント:companion object : JsonFactory<Order> { ... }およびトップレベルの拡張関数