O que é Companion Object em Kotlin?

Resposta rápida: o companion object é um objeto declarado dentro de uma classe cujos membros você acessa direto pelo nome da classe — Configuracao.VERSAO — sem criar instância. É o substituto Kotlin para os membros static do Java: como Kotlin não tem a palavra-chave static, constantes, factory methods e utilitários da classe vivem no companion object. Ele é instanciado uma única vez (lazy, no primeiro acesso) e pode ter nome, implementar interfaces e receber extension functions.

O companion object é um objeto especial declarado dentro de uma classe que funciona como o equivalente aos membros estáticos do Java. Com ele, você pode acessar propriedades e funções diretamente pelo nome da classe, sem precisar criar uma instância.

Kotlin não tem a palavra-chave static. Em vez disso, usa companion objects – uma abordagem mais flexível e orientada a objetos.

Pense no companion object como a “recepção” de um prédio comercial. Você não precisa entrar em nenhuma sala específica (instância) para obter informações gerais – basta perguntar na recepção. Da mesma forma, o companion object guarda informações e funcionalidades que pertencem à classe como um todo, não a uma instância individual.

Sintaxe básica

class Configuração {
    companion object {
        const val VERSAO = "2.1.0"
        val AMBIENTE = "producao"

        fun info(): String {
            return "Versão $VERSAO - Ambiente: $AMBIENTE"
        }
    }
}

fun main() {
    println(Configuração.VERSAO)    // 2.1.0
    println(Configuração.info())    // Versão 2.1.0 - Ambiente: producao
}

Repare que você acessa versão e info() direto pela classe, sem instanciar nada.

Factory methods

Um dos usos mais comuns do companion object é para criar factory methods – funções que constroem objetos de formas alternativas:

data class Usuario(val nome: String, val email: String) {
    companion object {
        fun criarAdmin(nome: String): Usuario {
            return Usuario(nome, "$nome@admin.com.br")
        }

        fun criarConvidado(): Usuario {
            return Usuario("Convidado", "[email protected]")
        }
    }
}

fun main() {
    val admin = Usuario.criarAdmin("karina")
    val guest = Usuario.criarConvidado()

    println(admin)  // Usuario(nome=karina, [email protected])
    println(guest)  // Usuario(nome=Convidado, [email protected])
}

Companion object com nome e interface

O companion object pode ter um nome e até implementar interfaces:

interface Serializavel {
    fun fromJson(json: String): Any
}

class Produto(val nome: String) {
    companion object Fabrica : Serializavel {
        override fun fromJson(json: String): Produto {
            return Produto(json) // simplificado
        }
    }
}

Quando usar?

Use companion object para constantes, factory methods, e qualquer funcionalidade que pertença à classe como um todo e não a uma instância específica. É o jeito Kotlin de lidar com o que seria static em Java.

Companion object, object, static ou constante top-level?

SituaçãoAlternativa recomendadaPor quê
Constante ligada à classe (Log.NIVEL_DEBUG)companion object com const valfica no namespace da classe, sem poluir o arquivo
Constante genérica do arquivo (const val PI)const val de nível superiormenos indireção, acesso direto
Singleton independente (Database, ApiClient)object de nível superiornão precisa de classe hospedeira
Factory method da classe (Usuario.fromCsv)companion object com funchamada idiomática Usuario.fromCsv(...)
Campo/método estático chamado de Javacompanion object + @JvmStatic/@JvmFieldinterop sem Companion. no meio do caminho
Vários agrupamentos utilitáriosmúltiplos object nomeadoscada classe aceita apenas um companion object

Companion object com extension functions

Você pode adicionar extension functions ao companion object de uma classe, o que permite estender funcionalidades sem modificar o código original:

class Validador {
    companion object
}

fun Validador.Companion.cpf(valor: String): Boolean {
    return valor.replace("[^0-9]".toRegex(), "").length == 11
}

fun Validador.Companion.email(valor: String): Boolean {
    return valor.contains("@") && valor.contains(".")
}

fun main() {
    println(Validador.cpf("123.456.789-00")) // true
    println(Validador.email("[email protected]")) // true
    println(Validador.email("invalido"))         // false
}

Companion object como singleton de configuração

Um padrão muito utilizado é usar companion object para gerenciar configurações ou registros de uma classe:

class Logger private constructor(val nome: String) {
    companion object {
        private val instancias = mutableMapOf<String, Logger>()

        fun obter(nome: String): Logger {
            return instancias.getOrPut(nome) {
                println("Criando logger para: $nome")
                Logger(nome)
            }
        }
    }

    fun info(mensagem: String) {
        println("[$nome] INFO: $mensagem")
    }
}

fun main() {
    val logA = Logger.obter("App")
    val logB = Logger.obter("App")    // reutiliza a instância existente
    val logC = Logger.obter("Database")

    logA.info("Aplicação iniciada")
    logC.info("Conexão estabelecida")

    println(logA === logB) // true -- mesma instância
}

Casos de Uso no Mundo Real

  • Constantes de configuração: agrupar URLs de API, chaves de preferências, códigos de erro e outras constantes que pertencem ao contexto da classe.
  • Factory methods com válidação: criar instâncias com regras de negócio, como Usuario.fromCsv(linha) ou Pedido.criarRascunho().
  • Interoperabilidade com Java: usar a anotação @JvmStatic em métodos do companion object para que o código Java possa chamar como métodos estáticos reais.
  • Serialização e desserialização: implementar funções fromJson() e fromMap() no companion object para converter dados externos em objetos Kotlin.
  • Registros e caches: manter um mapa de instâncias para evitar criação duplicada, como no padrão mostrado acima.

Boas Práticas

  • Use const val para constantes primitivas: dentro do companion object, declare constantes de tipos primitivos e String com const val para garantir que sejam inlined pelo compilador.
  • Não coloque lógica de negócio pesada no companion object. Ele deve conter funcionalidades utilitárias relacionadas à classe, não regras complexas.
  • Dê um nome ao companion object quando ele implementa uma interface ou quando você quer deixar o propósito claro: companion object Factory.
  • Use @JvmStatic em projetos mistos Kotlin/Java para facilitar a interoperabilidade.
  • Prefira object de nível superior quando a funcionalidade não está diretamente ligada a uma classe específica.

Erros Comuns

  • Confundir companion object com instância da classe: o companion object é compartilhado entre todas as instâncias. Alterar uma propriedade var dentro dele afeta todas as partes do código que a acessam.
  • Usar companion object para estado mutável global: isso cria acoplamento e dificulta testes. Se precisa de estado compartilhado, considere injeção de dependência.
  • Esquecer que cada classe só pode ter um companion object: se você precisa de múltiplos agrupamentos, use objects regulares nomeados dentro da classe.
  • Não usar const em constantes String: declarar val em vez de const val para strings constantes impede a otimização do compilador e gera um campo com getter desnecessário.

Perguntas Frequentes

Qual a diferença entre companion object e object declaration? O companion object vive dentro de uma classe e permite acessar seus membros pelo nome da classe (como MinhaClasse.função()). Um object declaration é um singleton independente, acessado pelo próprio nome.

Posso ter mais de um companion object por classe? Não. Cada classe pode ter no máximo um companion object. Se precisar de mais agrupamentos, use object nomeados regulares dentro da classe.

O companion object é inicializado quando? Ele é inicializado na primeira vez que a classe é referenciada, de forma similar ao carregamento de classes no Java. Isso segue o padrão de inicialização lazy.

Como acessar o companion object de Kotlin a partir do Java? Use MinhaClasse.Companion.método(). Se quiser acessar como método estático, adicione @JvmStatic ao método dentro do companion object.

Qual a diferença entre companion object e static do Java? O static do Java é um membro da classe sem instância, resolvido em tempo de compilação. O companion object é um objeto real (uma instância singleton da classe interna Companion), inicializado de forma lazy na primeira referência à classe. Por isso ele pode implementar interfaces, ter nome, estender outra classe e receber extension functions — coisas que static não faz. Em código Java, adicione @JvmStatic (para métodos) e @JvmField (para campos val) para gerar membros estáticos de verdade e evitar o prefixo Companion.

Companion object é inicializado quando a classe é carregada? Não exatamente. Diferente dos inicializadores static do Java, que rodam no carregamento da classe, o companion object é instanciado de forma lazy, apenas quando um membro dele é acessado pela primeira vez.

Próximos Passos