Objeto companion en Kotlin: reemplazo de Java static y rendimiento

Entrenador de Python en Línea para Principiantes

Aprende Python fácilmente sin sobrecargarte de teoría. Resuelve tareas prácticas con verificación automática, recibe pistas y escribe código directamente en el navegador, sin instalar nada.

Start Course

¿Por qué Kotlin no tiene static, pero sí companion object? Analizando el rendimiento y la lógica



Si estás migrando de Java a Kotlin, una de las primeras preguntas que surgirá en StackOverflow (y que realmente ha acumulado miles de visitas) es: «¿Por qué usamos companion object en lugar de Java static? ¿Y no ralentiza esto la aplicación?»



Seamos sinceros: cuando vi por primera vez la construcción companion object, pensé que el equipo de Kotlin solo quería complicar la vida. ¿Por qué escribir más código si podían simplemente copiar static de Java? Pero, investigando más a fondo, entendí: no es un capricho, sino una decisión arquitectónica bien pensada que hace que el código sea más limpio, seguro y… sí, a veces incluso más rápido.



En este artículo no solo traduciremos la respuesta de SO. Analizaremos cómo funciona companion object bajo el capó, compararemos el rendimiento con Java static y mostraremos cuándo usarlo y cuándo es mejor elegir otro camino.



¿Qué es companion object en palabras sencillas?



En Java, los campos y métodos estáticos pertenecen a la clase, no a la instancia. En Kotlin se decidió abandonar la palabra clave static porque viola los principios de la POO (lo estático no se puede sobrescribir, no es parte del objeto). En su lugar, Kotlin ofrece objetos acompañantes.



Imagina que tienes una clase User. Quieres almacenar una constante MAX_AGE y un método de fábrica createGuest(). En Java escribirías:



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


En Kotlin se ve así:



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


Observa: companion object no es solo un «bloque static». Es un objeto completo que vive dentro de tu clase. Puede tener interfaces, extensiones e incluso su propio nombre (si lo defines).



¿Cómo funciona companion object bajo el capó? (A través de los ojos de la JVM)



Para entender el rendimiento, echemos un vistazo al bytecode. Cuando escribes companion object, el compilador de Kotlin hace lo siguiente:



  • Crea una clase estática interna con el nombre Companion (o tu nombre personalizado).
  • Coloca todos los campos y métodos del companion object en esta clase.
  • Si usas la anotación @JvmStatic, el compilador genera adicionalmente métodos estáticos envoltorio en la propia clase.


Ejemplo con la anotación @JvmStatic:



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


Ahora desde Java puedes llamar a User.createGuest() como un método estático normal. Sin esta anotación tendrías que escribir User.Companion.createGuest().



Conclusión sobre el rendimiento: si trabajas solo con Kotlin, no hay gastos generales adicionales. Si lo llamas desde Java, hay un nivel extra de indirección (a través del objeto Companion). Pero el compilador JIT de la JVM inlinea muy bien estas llamadas, por lo que la diferencia de velocidad tiende a cero.



Rendimiento: companion object vs Java static



Despejemos el mito principal: companion object NO ralentiza. Realicé varios micro-benchmarks (en Kotlin 1.9, JVM 17), y esto es lo que obtuve:



Blogs

Book Recommendations

Escenario Java static