404 Not Found

404 Not Found


nginx

Kotlin Object Expressions and Companion Objects Explained

Kotlin's object keyword unifies three forms: singleton declarations, companion objects, and anonymous objects. Charlie uses companion object as a replacement for Java's static factory methods, and object declarations to replace singleton utility classes.

1. What You'll Learn


2. A Real Architect's Story

(1) Pain Point: Java Singletons and Static Methods Proliferation

Charlie's Java project had 10+ singleton classes (getInstance()) and a flood of static utility methods (static), making testing difficult and extension impossible.

(2) Kotlin object's Solution

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)
}

A single line of Kotlin replaces Java's 5-line singleton boilerplate, and it's naturally thread-safe.


3. object Declaration (Singleton)

(1) Basic Syntax

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) Singletons with State

KOTLIN
object OrderCounter {
    private var count = 0L

    fun increment() { count++ }
    fun getCount() = count
    fun reset() { count = 0 }
}

OrderCounter.increment()
println(OrderCounter.getCount())  // 1

(3) object vs Java Singleton

Dimension Java Singleton Kotlin object
Code volume 5+ lines 1 line
Thread safety Must ensure manually Naturally safe
Serialization Requires special handling Naturally correct
Testability Difficult Likewise difficult (global state)
Alternative Dependency injection Dependency injection (recommended)

4. companion object

(1) Basic Syntax

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) Named Companion Objects

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) companion object Implementing Interfaces

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) companion object vs Java static

Dimension Java static Kotlin companion object
Nature Class-level member Object (companion singleton)
Implementing interfaces Cannot Can
Extension functions Cannot Can
Naming Unnamed Can be named
Runtime existence Does not exist Exists (singleton object)

5. Object Expressions (Anonymous Objects)

(1) Basic Syntax

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) Anonymous Objects Capturing External Variables

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) Three object Forms Compared

Form Syntax Lifecycle Use Case
object declaration object Name { } Process-level singleton Global utilities / configuration
companion object class X { companion object { } } Class-level singleton Factory methods / constants
Object expression object : Interface { } Temporary instance One-off implementations

6. Practical Use Case Comparison

Scenario Recommended Form Example
Global configuration object declaration object AppConfig
Factory methods companion object Order.create(...)
One-off callbacks Object expression object : OrderHandler { }
Constant collections companion object + const const val MAX = 1_000_000
Utility functions object declaration or top-level function object OrderUtils
Multi-interface implementation Object expression object : A, B { }

7. Companion Object Extensions

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")
💡 Hint: Companion object extensions let third-party libraries add "static methods" to a class without modifying the class source code.


7. object Form Selection Flow

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. Complete Example

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))
}

Output:

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)

❓ FAQ

Q Can object singletons accept dependency injection?
A Technically yes (via var properties), but it's not recommended. Singleton global state makes testing difficult. For production code, prefer DI frameworks (Koin/Dagger) over object singletons.
Q Are companion object members actually static?
A On the JVM, methods annotated with @JvmStatic inside a companion object are compiled as true static methods. Without the annotation, they remain instance methods on the companion object.
Q Can anonymous objects access external variables?
A Yes. Unlike Java anonymous inner classes, Kotlin anonymous objects can access and modify external var variables (like closures).
Q How do I choose between companion object and object declaration?
A Use companion object for things tightly coupled to a class (factory methods, constants). Use object declarations for things independent of any class (global utilities, configuration).
Q What's the difference between const val and plain val in a companion object?
A const val is a compile-time constant (inlined at call sites), applicable only to primitives and String. Plain val is initialized at runtime.
Q Can a companion object have a supertype?
A Yes. Companion objects can implement interfaces, making the factory pattern more elegant: companion object : JsonFactory<Order>.

📖 Summary


📝 Exercises

  1. Beginner (⭐): Create an OrderCounter singleton with increment() and getCount() methods. Hint: object OrderCounter { ... }
  2. Intermediate (⭐⭐): Add a companion object to the Order class with a fromCsv factory method that parses a CSV line to create an Order. Hint: companion object { fun fromCsv(line: String): Order { ... } }
  3. Challenge (⭐⭐⭐): Implement companion object interface JsonFactory<Order> + companion object extension Order.Companion.fromXml(). Hint: companion object : JsonFactory<Order> { ... } + top-level extension function

← Previous | Next →

Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏