¿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:
| Escenario | Java static |
|---|