Kotlin: Expressões Object e Companion Objects do Kotlin…

Última atualização: 2026-08-26

A palavra-chave object do Kotlin unifica três formas: declarações singleton, companion objects, e objetos anônimos. Charlie usa companion object como substituto dos métodos de fábrica static do Java, e declarações object para substituir classes utilitárias singleton.

1. O que Você Aprenderá


2. A História Real de um Arquiteto

(1) Ponto de Dor: Singletons e Métodos Estáticos do Java em Proliferação

O projeto Java de Charlie tinha 10+ classes singleton (getInstance()) e uma enxurrada de métodos utilitários estáticos (static), tornando testes difíceis e extensão impossível.

(2) Solução com object do Kotlin

KOTLIN
// Java: singleton verboso
public class OrderValidator {
    private static final OrderValidator INSTANCE = new OrderValidator();
    private OrderValidator() {}
    public static OrderValidator getInstance() { return INSTANCE; }
}

// Kotlin: singleton em 1 linha
object OrderValidator {
    fun validate(order: Order) = require(order.total >= 0)
}

Uma única linha de Kotlin substitui o boilerplate de singleton de 5 linhas do Java, e é naturalmente thread-safe.


3. Declaração object (Singleton)

(1) Sintaxe Básica

KOTLIN
// Singleton: apenas uma instância, inicialização thread-safe
object OrderValidator {
    fun validate(order: Order): Boolean {
        return order.total >= 0 && order.id.startsWith("ORD-")
    }
}

// Uso: acessar diretamente pelo nome do objeto
OrderValidator.validate(order)  // Sem necessidade de getInstance()

(2) Singletons com Estado

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 Singleton do Java

Dimensão Singleton Java Kotlin object
Volume de código 5+ linhas 1 linha
Thread safety Deve garantir manualmente Naturalmente seguro
Serialização Requer tratamento especial Naturalmente correto
Testabilidade Difícil Igualmente difícil (estado global)
Alternativa Injeção de dependência Injeção de dependência (recomendado)

4. companion object

(1) Sintaxe Básica

KOTLIN
class Order(val id: String, val total: Double, val status: String) {
    companion object {
        // Métodos de fábrica
        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])
        }

        // Constantes
        const val DEFAULT_STATUS = "PENDING"
        const val MAX_TOTAL = 1_000_000.0
    }
}

// Uso: como static no Java
val order = Order.create("ORD-001", 299.99)
val csvOrder = Order.fromCsv("ORD-002,1500.00,CONFIRMED")
println(Order.DEFAULT_STATUS)

(2) Companion Objects Nomeados

KOTLIN
class Order(val id: String, val total: Double) {
    companion object Factory {
        fun create(id: String, total: Double) = Order(id, total)
    }
}

// Pode usar qualquer nome
Order.create("ORD-001", 299.99)
Order.Factory.create("ORD-001", 299.99)

(3) companion object Implementando 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 {
            // Parsear json e criar Order
            return Order("ORD-from-json", 0.0)
        }
    }
}

// Uso polimórfico
val factory: JsonFactory<Order> = Order
val order = factory.fromJson("{\"id\":\"ORD-001\"}")

(4) companion object vs static do Java

Dimensão Java static Kotlin companion object
Natureza Membro em nível de classe Objeto (singleton companion)
Implementar interfaces Não pode Pode
Funções de extensão Não pode Pode
Nomeação Sem nome Pode ser nomeado
Existência em tempo de execução Não existe Existe (objeto singleton)

5. Expressões Object (Objetos Anônimos)

(1) Sintaxe Básica

KOTLIN
// Objeto anônimo: como classe interna anônima do Java
val handler = object : OrderHandler {
    override fun handle(order: Order) {
        println("Tratando ${order.id}")
    }
}

// Objeto anônimo implementando múltiplas interfaces
val multiHandler = object : OrderHandler, Loggable {
    override fun handle(order: Order) { println("Tratar: ${order.id}") }
    override fun log(message: String) { println("[LOG] $message") }
}

(2) Objetos Anônimos Capturando Variáveis Externas

KOTLIN
fun createHandler(prefix: String): OrderHandler {
    var count = 0  // Capturado pelo objeto anônimo
    return object : OrderHandler {
        override fun handle(order: Order) {
            count++
            println("$prefix [#$count] ${order.id}")
        }
    }
}

(3) Três Formas de object Comparadas

Forma Sintaxe Ciclo de Vida Caso de Uso
Declaração object object Name { } Singleton em nível de processo Utilitários globais / configuração
companion object class X { companion object { } } Singleton em nível de classe Métodos de fábrica / constantes
Expressão object object : Interface { } Instância temporária Implementações de uso único

6. Comparação de Casos de Uso Práticos

Cenário Forma Recomendada Exemplo
Configuração global Declaração object object AppConfig
Métodos de fábrica companion object Order.create(...)
Callbacks de uso único Expressão object object : OrderHandler { }
Coleções de constantes companion object + const const val MAX = 1_000_000
Funções utilitárias Declaração object ou função de nível superior object OrderUtils
Implementação multi-interface Expressão object object : A, B { }

7. Extensões de Companion Object

KOTLIN
class Order(val id: String, val total: Double) {
    companion object  // Vazio - existe como alvo de extensão
}

// Extensão no companion object
fun Order.Companion.fromCsv(line: String): Order {
    val parts = line.split(",")
    return Order(parts[0], parts[1].toDouble())
}

// Uso
val order = Order.fromCsv("ORD-001,299.99")
💡 Dica: Extensões de companion object permitem que bibliotecas de terceiros adicionem "métodos estáticos" a uma classe sem modificar o código-fonte da classe.


7. Fluxo de Seleção de Forma object

100%
flowchart TD
    A[Precisa de objeto?] --> B{Fortemente ligado a uma classe?}
    B -->|Sim| C{Precisa de fábrica/constantes?}
    B -->|Não| D{Precisa de instância única?}
    C -->|Sim| E[companion object]
    C -->|Não| F{Precisa de singleton global?}
    D -->|Sim| G[Expressão Object<br/>object : Interface]
    D -->|Não| H[Instância de classe regular]
    F -->|Sim| I[Declaração object<br/>Singleton]
    F -->|Não| H

8. Exemplo Completo

KOTLIN
// ============================================
// OrderProcessor - Padrões com Object
// Recurso: Singleton, Companion Factory, Handler Anônimo
// ============================================

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 deve ser não-negativo" }
            require(total <= MAX_TOTAL) { "Total excede o máximo" }
            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) }
        }
    }
}

// Validador singleton
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("Formato de ID inválido")
        if (order.total < 0) errors.add("Total negativo")
        rules.forEach { rule ->
            if (!rule(order)) errors.add("Regra personalizada falhou")
        }
        return errors
    }
}

// Contador singleton
object OrderMetrics {
    private var totalProcessed = 0L
    private var totalRevenue = 0.0

    @Synchronized fun record(order: Order) {
        totalProcessed++
        totalRevenue += order.total
    }

    fun report() = "Processados: $totalProcessed pedidos, Receita: \$$totalRevenue USD"
}

interface OrderHandler {
    fun handle(order: Order): String
}

fun main() {
    // Fábrica companion
    println("=== Criação por Fábrica ===")
    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) }

    // Validador singleton
    println("\n=== Validação ===")
    OrderValidator.addRule { it.total > 0 }
    val validOrder = Order("ORD-100", 299.99, "PENDING", "Alice")
    val invalidOrder = Order("BAD-001", -50.0, "PENDING", "Bob")

    println("Válido: ${OrderValidator.validate(validOrder)}")
    println("Inválido: ${OrderValidator.validate(invalidOrder)}")

    // Métricas singleton
    println("\n=== Métricas ===")
    listOf(order1, order2).forEach { OrderMetrics.record(it) }
    println(OrderMetrics.report())

    // Handler anônimo
    println("\n=== Handler Anônimo ===")
    val shippingHandler = object : OrderHandler {
        private var shipped = 0
        override fun handle(order: Order): String {
            shipped++
            order.status = "SHIPPED"
            return "Enviado ${order.id} (Total enviado: $shipped)"
        }
    }

    println(shippingHandler.handle(order1))
    println(shippingHandler.handle(order2))
}

Saída:

TEXT 📖 Somente leitura
=== Criação por Fábrica ===
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)

=== Validação ===
Válido: []
Inválido: [Formato de ID inválido, Total negativo, Regra personalizada falhou]

=== Métricas ===
Processados: 2 pedidos, Receita: $15299.99 USD

=== Handler Anônimo ===
Enviado ORD-001 (Total enviado: 1)
Enviado ORD-002 (Total enviado: 2)

9. Exemplos práticos rápidos

▶ Exemplo: Object expression (classe anônima)

KOTLIN
// Object expression: cria instância de classe anônima
interface ClickHandler {
    fun onClick(event: String)
}

val handler = object : ClickHandler {
    override fun onClick(event: String) {
        println("Click recebido: $event")
    }
}

handler.onClick("button-login")

// Herança de uma única classe
open class Event(val type: String)

val myEvent = object : Event("USER_LOGIN") {
    val userId = "u-001"
    fun describe() = "$type:$userId"
}

println(myEvent.describe())

▶ Exemplo: Singleton com object declaration

KOTLIN
// object declaration: singleton thread-safe por padrão
object Logger {
    private val logs = mutableListOf<String>()

    init {
        println("Logger inicializado (hash=${System.identityHashCode(this)})")
    }

    fun log(level: String, message: String) {
        logs.add("[$level] $message")
    }

    fun show() = logs.forEach(::println)
}

Logger.log("INFO", "App iniciada")
Logger.log("ERROR", "Falha de conexão")

// Mesma instância sempre
val loggerRef1 = Logger
val loggerRef2 = Logger
println("Mesma instância: ${loggerRef1 === loggerRef2}")

Logger.show()

▶ Exemplo: Companion object - factory e constantes

KOTLIN
class ApiClient private constructor(val baseUrl: String) {
    companion object {
        private const val DEFAULT_TIMEOUT = 30

        // Factory methods
        fun production() = ApiClient("https://api.prod.com")
        fun development() = ApiClient("http://localhost:8080")
        fun staging() = ApiClient("https://api.staging.com")

        // Constantes acessíveis como ApiClient.MAX_RETRIES
        const val MAX_RETRIES = 3
    }

    init {
        println("Cliente criado: $baseUrl (timeout=$DEFAULT_TIMEOUT, retries=$MAX_RETRIES)")
    }

    override fun toString() = "ApiClient($baseUrl)"
}

val prod = ApiClient.production()
val dev = ApiClient.development()
println("Max retries: ${ApiClient.MAX_RETRIES}")

▶ Exemplo: Companion implementando interface

KOTLIN
// Companion pode implementar interface
interface Factory<T> {
    fun create(): T
}

class Order(val id: String) {
    companion object : Factory<Order> {
        override fun create(): Order = Order("ORD-${System.currentTimeMillis() % 10000}")
    }
}

val order = Order.create()
println("Criado: $order")
println("Factory é Companion: ${Order.Companion is Factory<Order>}")

// Outro exemplo - logger estático
data class Product(val sku: String)

interface Serializer<T> {
    fun toJson(value: T): String
}

class ProductSerializer {
    companion object : Serializer<Product> {
        override fun toJson(value: Product) = """{"sku":"${value.sku}"}"""
    }
}

println(ProductSerializer.toJson(Product("SKU-001")))

▶ Exemplo: Extensões em companion

KOTLIN
class StringHelper {
    companion object
}

// Extensão em companion object
fun StringHelper.Companion.capitalize(s: String) = s.replaceFirstChar { it.uppercase() }
fun StringHelper.Companion.isPalindrome(s: String) = s == s.reversed()

println(StringHelper.capitalize("olá mundo"))
println(StringHelper.isPalindrome("arara"))

▶ Exemplo: Múltiplos objetos em uma expressão

KOTLIN
// Comparação entre dois objetos anônimos
val loggerA = object {
    val prefix = "A"
    fun log(msg: String) = println("[$prefix] $msg")
}

val loggerB = object {
    val prefix = "B"
    fun log(msg: String) = println("[$prefix] $msg")
}

loggerA.log("Hello")
loggerB.log("World")

▶ Exemplo: Companion nomeado

KOTLIN
class NetworkClient(val url: String) {
    companion object HttpHelper {
        fun build(host: String, path: String) = "https://$host$path"
        val DEFAULT_HEADERS = mapOf("User-Agent" to "Kotlin-Client/1.0")
    }
}

// Acessando tanto pelo nome quanto pela classe
println(NetworkClient.HttpHelper.build("api.example.com", "/v1/orders"))
println("Headers: ${NetworkClient.DEFAULT_HEADERS}")
println(NetworkClient.HttpHelper.DEFAULT_HEADERS["User-Agent"])

❓ Perguntas Frequentes

P: Singletons object podem aceitar injeção de dependência? R: Tecnicamente sim (via propriedades var), mas não é recomendado. Estado global singleton torna testes difíceis. Para código de produção, prefira frameworks DI (Koin/Dagger) em vez de singletons object.

P: Membros de companion object são realmente estáticos? R: Na JVM, métodos anotados com @JvmStatic dentro de um companion object são compilados como verdadeiros métodos estáticos. Sem a anotação, permanecem métodos de instância no companion object.

P: Objetos anônimos podem acessar variáveis externas? R: Sim. Diferente das classes internas anônimas do Java, objetos anônimos do Kotlin podem acessar e modificar variáveis var externas (como closures).

P: Como escolher entre companion object e declaração object? R: Use companion object para coisas fortemente acopladas a uma classe (métodos de fábrica, constantes). Use declarações object para coisas independentes de qualquer classe (utilitários globais, configuração).

P: Qual a diferença entre const val e val simples em um companion object? R: const val é uma constante em tempo de compilação (inserida nos locais de chamada), aplicável apenas a primitivos e String. val simples é inicializado em tempo de execução.

P: Um companion object pode ter um supertipo? R: Sim. Companion objects podem implementar interfaces, tornando o padrão factory mais elegante: companion object : JsonFactory<Order>.


📖 Resumo


📝 Exercícios

  1. Iniciante (⭐): Crie um singleton OrderCounter com métodos increment() e getCount(). Dica: object OrderCounter { ... }
  2. Intermediário (⭐⭐): Adicione um companion object à classe Order com um método de fábrica fromCsv que parseia uma linha CSV para criar um Order. Dica: companion object { fun fromCsv(line: String): Order { ... } }
  3. Desafio (⭐⭐⭐): Implemente interface de companion object JsonFactory<Order> + extensão de companion object Order.Companion.fromXml(). Dica: companion object : JsonFactory<Order> { ... } + função de extensão de nível superior

← Anterior | Próximo →

Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%