Objeto companion em Kotlin: substituição do Java static e desempenho

Treinador Online de Python para Iniciantes

Aprenda Python facilmente sem sobrecarga teórica. Resolva tarefas práticas com verificação automática e escreva código diretamente no navegador.

Start Course

Por que o Kotlin não tem static, mas tem companion object? Entendendo desempenho e lógica



Se você está migrando do Java para o Kotlin, uma das primeiras perguntas que surgirá no StackOverflow (e que realmente acumulou milhares de visualizações) é: «Por que usamos companion object em vez do Java static? E isso não deixa a aplicação lenta?»



Vamos ser honestos: quando vi pela primeira vez a construção companion object, pensei que a equipe do Kotlin só queria complicar a vida. Por que escrever mais código se poderiam simplesmente copiar o static do Java? Mas, investigando mais a fundo, entendi: não é um capricho, mas uma decisão arquitetural bem pensada que torna o código mais limpo, seguro e… sim, às vezes até mais rápido.



Neste artigo, não vamos apenas traduzir a resposta do SO. Vamos analisar como o companion object funciona internamente, comparar o desempenho com o Java static e mostrar quando usá-lo, e quando é melhor escolher outro caminho.



O que é companion object em termos simples?



Em Java, campos e métodos estáticos pertencem à classe, não à instância. O Kotlin decidiu abandonar a palavra-chave static porque ela viola os princípios de POO (o estático não pode ser sobrescrito, não faz parte de um objeto). Em vez disso, o Kotlin oferece objetos companheiros.



Imagine que você tem uma classe User. Você quer armazenar uma constante MAX_AGE e um método de fábrica createGuest(). Em Java, você escreveria:



public class User {    public static final int MAX_AGE = 120;    public static User createGuest() {        return new User("guest", "1234");    }}


Em Kotlin, fica assim:



class User(private val name: String, private val password: String) {    companion object {        const val MAX_AGE = 120        fun createGuest() = User("guest", "1234")    }}


Observe: companion object não é apenas um «bloco static». É um objeto completo que vive dentro da sua classe. Ele pode ter interfaces, extensões e até um nome próprio (se você definir).



Como o companion object funciona internamente? (Pelos olhos da JVM)



Para entender o desempenho, vamos dar uma olhada no bytecode. Quando você escreve um companion object, o compilador Kotlin faz o seguinte:



  • Cria uma classe estática interna com o nome Companion (ou o nome personalizado que você definir).
  • Coloca todos os campos e métodos do companion object dentro dessa classe.
  • Se você usar a anotação @JvmStatic, o compilador gera adicionalmente métodos estáticos wrapper na própria classe.


Exemplo com a anotação @JvmStatic:



class User {    companion object {        @JvmStatic        fun createGuest() = User()    }}


Agora, do Java, você pode chamar User.createGuest() como um método estático normal. Sem essa anotação, você teria que escrever User.Companion.createGuest().



Conclusão sobre desempenho: se você trabalha apenas com Kotlin, não há custo adicional. Se chamar do Java, há um nível extra de indireção (através do objeto Companion). Mas o compilador JIT da JVM otimiza muito bem essas chamadas, então a diferença de velocidade tende a zero.



Desempenho: companion object vs Java static



Vamos desfazer o mito principal: companion object NÃO deixa lento. Realizei alguns micro-benchmarks (no Kotlin 1.9, JVM 17), e aqui estão os resultados:



Blogs

Book Recommendations

Cenário Java static