Cómo dejar de sufrir y encontrar el reemplazo de Java static en Kotlin
\n\n¿Alguna vez ha reescrito un viejo y querido código Java a Kotlin y de repente se ha topado con una pared? Todo parece simple, lógico, conciso… Pero entonces quiere crear un método estático y Kotlin dice: «No, amigo, yo no tengo eso». ¿Pánico? En vano. En Stack Overflow esta pregunta ha acumulado miles de visitas, y no en vano: el tema es realmente importante para todo aquel que migra de Java a Kotlin.
\n\nEn este artículo no solo daremos una respuesta seca. Analizaremos el equivalente de los métodos estáticos de Java en Kotlin desde el punto de vista del rendimiento, la optimización y la facilidad de mantenimiento. Aprenderá cómo no perder velocidad y no pisar el rastrillo. ¡Allá vamos!
\n\n¿Por qué Kotlin no tiene static y para qué sirve?
\n\nVayamos al grano. En Java, los métodos static son métodos que pertenecen a la clase, no a su instancia. Viven en la memoria una sola vez, no requieren la creación de un objeto y son ideales para funciones utilitarias, fábricas o singletons.
\n\n// Java\npublic class MathUtils {\n public static int square(int x) {\n return x * x;\n }\n}\n\n// Uso\nint result = MathUtils.square(5);\n\n\nEn Kotlin, en cambio, no existe la palabra clave static. ¿Por qué? Porque Kotlin fomenta un enfoque orientado a objetos y funcional al mismo tiempo. En lugar de static, aquí se utilizan:
\n\n- \n
- companion object (objeto acompañante) \n
- funciones top-level (funciones fuera de la clase) \n
- object (objeto singleton) \n
Cada opción tiene sus propios matices de rendimiento y optimización. Vamos a analizarlos en orden.
\n\nOpción 1: companion object — ¿el análogo directo de static?
\n\nLa respuesta más común que encontrará en Stack Overflow es companion object. Se ve así:
\n\n// Kotlin\nclass MathUtils {\n companion object {\n fun square(x: Int): Int = x * x\n }\n}\n\n// Uso\nval result = MathUtils.square(5)\n\n\nVisualmente, es idéntico a Java. Pero bajo el capó es diferente. companion object es un objeto real, descendiente de Any. Se crea una vez al cargar la clase y se almacena en la memoria como un objeto normal. Esto significa que:
- \n
- Memoria: un poco más que en Java static (debido a la sobrecarga del objeto). \n
- Llamada: el compilador de Kotlin genera un método estático, pero solo si se añade la anotación
@JvmStatic. \n
@JvmStatic: la magia de la interoperabilidad
\n\nSi escribe en Kotlin, pero su código es llamado desde Java, sin @JvmStatic se encontrará con una desagradable sorpresa:
// Kotlin\nclass MyClass {\n companion object {\n @JvmStatic\n fun greet() = "¡Hola!"\n }\n}\n\n// Llamada desde Java\nMyClass.greet(); // Funciona gracias a @JvmStatic\n// Sin @JvmStatic habría que escribir MyClass.Companion.greet()\n\n\nDesde el punto de vista del rendimiento, @JvmStatic se compila en un método estático real en la JVM. Esto significa que obtiene exactamente la misma velocidad que en Java. Sin esta anotación, la llamada pasa a través del objeto Companion, lo que genera una micro-sobrecarga (menos de 1 nanosegundo, pero para sistemas de alta carga esto puede ser relevante).
Opción 2: Funciones top-level — el camino de Kotlin
\n\nKotlin permite declarar funciones directamente en el archivo, fuera de la clase. Esto se llama función top-level. El compilador las convierte en métodos estáticos de una clase envoltorio (nombre del archivo + Kt).
\n\n// Archivo: MathUtils.kt\nfun square(x: Int): Int = x * x\n\n// Uso\nval result = square(5)\n\n\nBajo el capó esto es:
\n\n// Java (decompilado)\npublic final class MathUtilsKt {\n public static final int squa