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á
- Declarações
object: padrão singleton do Kotlin companion object: métodos de fábrica e substituição de membros estáticos- Expressões object: implementações anônimas
- Extensões de companion object: funções de extensão em nível de classe
- Charlie em ação: fábrica
Order+ singletonOrderValidator
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
// 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
// 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
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
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
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
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
// 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
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
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")
7. Fluxo de Seleção de Forma object
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
// ============================================
// 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:
=== 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)
// 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
// 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
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
// 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
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
// 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
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
@JvmStaticdentro 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
varexternas (como closures).
P: Como escolher entre companion object e declaração object? R: Use
companion objectpara coisas fortemente acopladas a uma classe (métodos de fábrica, constantes). Use declaraçõesobjectpara coisas independentes de qualquer classe (utilitários globais, configuração).
P: Qual a diferença entre
const valevalsimples em um companion object? R:const valé uma constante em tempo de compilação (inserida nos locais de chamada), aplicável apenas a primitivos eString.valsimples é 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
- Declarações
objectcriam singletons thread-safe, substituindo o boilerplate de singleton de 5+ linhas do Java companion objectsubstituistaticdo Java, com mais poder (interfaces, extensões, nomeação)- Expressões object
object : Interface { }substituem classes internas anônimas do Java - Extensões de companion object permitem adicionar "métodos estáticos" sem modificar o código-fonte
- Singletons são difíceis de testar; prefira injeção de dependência a singletons object em produção
const valpara constantes em tempo de compilação vsvalpara inicialização em tempo de execução
📝 Exercícios
- Iniciante (⭐): Crie um singleton
OrderCountercom métodosincrement()egetCount(). Dica:object OrderCounter { ... } - Intermediário (⭐⭐): Adicione um
companion objectà classeOrdercom um método de fábricafromCsvque parseia uma linha CSV para criar um Order. Dica:companion object { fun fromCsv(line: String): Order { ... } } - Desafio (⭐⭐⭐): Implemente interface de companion object
JsonFactory<Order>+ extensão de companion objectOrder.Companion.fromXml(). Dica:companion object : JsonFactory<Order> { ... }+ função de extensão de nível superior