Kotlin: Kotlinのオブジェクト式とコンパニオンオブジェクト

最終更新:2026-08-26

Kotlinのobjectキーワードは、シングルトンの宣言、コンパニオンオブジェクト、および匿名オブジェクトという3つの形式を統合したものです。 Charlieは、Javaのstaticファクトリメソッドの代わりにcompanion objectを使用し、シングルトンユーティリティクラスの代わりにobject宣言を使用しています。

1. 学習内容


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. オブジェクトのフォーム選択フロー

100%
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>

📖 まとめ


📝 練習問題

  1. 初心者 (⭐): OrderCounter シングルトンを作成し、increment() および getCount() メソッドを実装してください。ヒント: object OrderCounter { ... }
  2. 中級 (⭐⭐): Order クラスに companion object を追加し、CSV の行を解析して Order を作成する fromCsv ファクトリメソッドを実装してください。ヒント: companion object { fun fromCsv(line: String): Order { ... } }
  3. 課題 (⭐⭐⭐): コンパニオンオブジェクトのインターフェース JsonFactory<Order> およびコンパニオンオブジェクトの拡張 Order.Companion.fromXml() を実装してください。ヒント: companion object : JsonFactory<Order> { ... } およびトップレベルの拡張関数

← 前へ | 次へ →

Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%