companion object es un mecanismo del lenguaje Kotlin que reemplaza la palabra clave static de Java para declarar miembros estáticos de una clase. Kotlin no tiene static incorporado — en su lugar, se usa una declaración object con el modificador companion dentro de una clase. Según JetBrains, 2025, companion object permite llamar métodos y propiedades a través del nombre de la clase, siendo el equivalente completo de los miembros estáticos en el ecosistema Java.
Puntos Clave
companion object es un tipo especial de object declaration en Kotlin que se marca con la palabra clave companion. Permite declarar miembros que pertenecen a la clase en lugar de a sus instancias — es decir, miembros estáticos en términos de Java. A diferencia de Java, donde static es un modificador para campos y métodos individuales, en Kotlin los miembros estáticos se agrupan dentro de un único objeto compañero.
Cualquier clase en Kotlin puede contener exactamente un companion object. Los miembros de este objeto son accesibles a través del nombre de la clase sin crear una instancia: MyClass.method(). En términos de bytecode, un companion object se compila como una clase interna separada, y sus métodos se convierten en métodos estáticos de la clase externa al usar la anotación @JvmStatic.
Según Google I/O 2024, companion object se ha convertido en el patrón principal para métodos fábrica, constantes y funciones auxiliares en aplicaciones Android modernas escritas en Kotlin. Los desarrolladores lo eligen sobre las clases utilitarias con funciones a nivel de paquete porque companion object mantiene una conexión lógica con la clase propietaria.
Utiliza companion object para agrupar contexto estático — constantes, fábricas, métodos auxiliares que pertenecen lógicamente a la clase pero no requieren una instancia.
La sintaxis básica de companion object es simple: la palabra clave companion se coloca antes de la declaración object dentro de la clase. Si no se especifica un nombre, el objeto recibe el nombre Companion, al que se puede acceder explícita o implícitamente.
class User {
companion object {
const val TABLE_NAME = "users"
fun create(name: String): User {
return User(name)
}
}
val name: String
constructor(name: String) { this.name = name }
}
// Access via class name
val table = User.TABLE_NAME
val user = User.create("Alice")
En el ejemplo anterior, TABLE_NAME y create() son accesibles mediante User.TABLE_NAME y User.create(). Observa el modificador const para constantes primitivas — garantiza la inserción del valor en tiempo de compilación, similar a Java static final para primitivos y String.
Si un companion object no tiene nombre, se puede acceder a él mediante el nombre automático Companion: User.Companion.TABLE_NAME. En la práctica esto rara vez es necesario, pero es útil al llamar desde código Java o al usar reflexión.
// Both calls are equivalent
User.create("Bob")
User.Companion.create("Bob")
// Getting reference to companion object
val companion: User.Companion = User
Un companion object puede tener un nombre, lo que mejora la legibilidad del código y permite acceder a él mediante un nombre significativo. Factory es el nombre más común para un companion object utilizado como fábrica.
class HttpResponse {
companion object Factory {
fun ok(body: String): HttpResponse = HttpResponse(200, body)
fun notFound(): HttpResponse = HttpResponse(404, "Not Found")
fun serverError(): HttpResponse = HttpResponse(500, "Internal Error")
}
}
// Access via Factory name
val response = HttpResponse.Factory.ok("data")
Un companion object con nombre ayuda a documentar el propósito de los miembros estáticos. En refactorización, el nombre hace que el código se autodocumente — el desarrollador ve instantáneamente que Factory crea instancias de HttpResponse.
Una clase puede contener solo un companion object. Esta es una diferencia fundamental con Java, donde se puede declarar cualquier cantidad de campos y métodos estáticos sin agrupar. Si se necesitan múltiples grupos lógicos de miembros estáticos, se usan declaraciones object anidadas sin companion.
Según la documentación oficial de Kotlin (Kotlin Docs, 2025), la limitación de un solo companion object está motivada por el diseño del lenguaje: los miembros estáticos deben estar estrechamente acoplados con la clase, y un objeto compañero proporciona un límite claro para esta conexión. Si hay demasiados miembros estáticos, es una señal para refactorizar la clase.
En Kotlin, las interfaces también pueden contener un companion object. Esto permite definir métodos estáticos y constantes directamente dentro de una interfaz, algo que no es posible en Java. Este enfoque se utiliza a menudo para declarar constantes relacionadas con la interfaz o métodos fábrica.
interface ApiService {
companion object {
const val BASE_URL = "https://api.example.com"
const val TIMEOUT_MS = 5000
fun create(client: OkHttpClient): ApiService =
Retrofit.Builder()
.baseUrl(BASE_URL)
.client(client)
.build()
.create(ApiService::class.java)
}
}
En este ejemplo, BASE_URL, TIMEOUT_MS y create() pertenecen a la interfaz ApiService, no a sus implementaciones. Esto es conveniente: todas las constantes y la lógica de fábrica para crear una instancia del servicio API están ubicadas en un solo lugar — dentro de la propia interfaz.
Las implementaciones de la interfaz no heredan los miembros del companion object — se llaman solo a través del nombre de la interfaz: ApiService.BASE_URL.
Al llamar a métodos de companion object desde código Java, por defecto son accesibles como métodos de la clase interna Companion, no como métodos estáticos de la clase externa. Para exportarlos como métodos y campos estáticos reales para Java, usa la anotación @JvmStatic para métodos y @JvmField para campos.
| Anotación | Aplicación | Resultado en Java |
|---|---|---|
| @JvmStatic | Métodos de companion object | Método estático: ClassName.method() |
| @JvmField | Campos de companion object | Campo estático: ClassName.field |
| const | Primitivos y String | Constante inline: el compilador sustituye el valor |
| sin anotación | Métodos/campos por defecto | Companion.method() / Companion.field |
class MathUtils {
companion object {
const val PI = 3.14159
@JvmStatic
fun square(x: Int): Int = x * x
@JvmField
val TAG: String = "MathUtils"
}
}
// Called in Java as MathUtils.square(5), MathUtils.TAG
Se recomienda usar @JvmStatic para todos los métodos públicos de companion object que deban ser accesibles desde código Java. const se aplica solo a tipos primitivos y String — para otros tipos usa @JvmField.
En proyectos reales, companion object se utiliza para varios escenarios estándar. Métodos fábrica es el patrón más común: en lugar de múltiples constructores con diferentes firmas, se usan métodos fábrica con nombre en companion object.
sealed class NetworkResult<out T> {
data class Success<out T>(val data: T) : NetworkResult<T>()
data class Error(val message: String) : NetworkResult<Nothing>()
companion object {
fun loading<T>(): NetworkResult<T> =
Loading()
}
}
private class Loading<T> : NetworkResult<T>()
companion object en NetworkResult proporciona un método fábrica loading() que crea una instancia Loading con el tipo genérico correcto. Sin este método, tendrías que crear una instancia directamente mediante Loading(), lo que expone la implementación interna.
companion object se usa a menudo para almacenar constantes específicas de la clase. Las constantes declaradas con const val se insertan en tiempo de compilación, proporcionando cero sobrecarga en tiempo de ejecución.
class UserRepository {
companion object {
private const val TAG = "UserRepository"
private const val CACHE_SIZE = 100
private const val DEFAULT_PAGE_SIZE = 20
}
}
Preguntas Frecuentes
Un object normal es un singleton completo que existe independientemente de la clase. companion object es un object dentro de una clase cuyos miembros se llaman a través del nombre de la clase externa. En bytecode, un companion object se convierte en una clase anidada estática, mientras que un object normal se convierte en una clase singleton autónoma.
No, companion object no se hereda por subclases. Si la clase B extiende la clase A, entonces B.method() no llamará al método del companion object de la clase A — debes llamar a A.method(). Esto coincide con el comportamiento de los métodos estáticos en Java.
Por defecto — mediante ClassName.Companion.method(). Para llamarlo como método estático, añade @JvmStatic al método del companion object. Para campos, usa @JvmField o decláralos con const val para primitivos.
Los desarrolladores de Kotlin decidieron abandonar static en favor de companion object para un trabajo uniforme con objetos. static en Java viola los principios de POO porque los métodos estáticos no están vinculados a una instancia. companion object es un objeto de primera clase que se puede pasar, extender con funciones de extensión e implementar interfaces.
Al usar const val para primitivos y String — cero sobrecarga, el valor se inserta en el bytecode. Para métodos, no hay sobrecarga a menos que el método sea inline. En la mayoría de los casos, companion object no crea ningún impacto medible en el rendimiento. Usa @JvmStatic solo para API pública llamada desde Java.
Resumen
Desarrollaremos una aplicación móvil llave en mano
IT Sectr crea aplicaciones para iOS y Android para startups y empresas desde 2017. Le asesoraremos y le propondremos la mejor solución.
Lea también