companion object è un meccanismo del linguaggio Kotlin che sostituisce la parola chiave static di Java per dichiarare membri statici di una classe. Kotlin non ha static integrato — invece, si usa una dichiarazione object con il modificatore companion all'interno di una classe. Secondo JetBrains, 2025, companion object permette di chiamare metodi e proprietà attraverso il nome della classe, rendendoli il pieno equivalente dei membri statici nell'ecosistema Java.
Punti Chiave
companion object è un tipo speciale di dichiarazione object in Kotlin contrassegnato dalla parola chiave companion. Permette di dichiarare membri che appartengono alla classe piuttosto che alle sue istanze — cioè membri statici in termini Java. A differenza di Java, dove static è un modificatore per campi e metodi individuali, in Kotlin i membri statici sono raggruppati all'interno di un unico oggetto compagno.
Qualsiasi classe in Kotlin può contenere esattamente un companion object. I membri di questo oggetto sono accessibili tramite il nome della classe senza creare un'istanza: MyClass.method(). In termini di bytecode, un companion object viene compilato come una classe interna separata e i suoi metodi diventano metodi statici della classe esterna quando si utilizza l'annotazione @JvmStatic.
Secondo Google I/O 2024, companion object è diventato il modello principale per metodi factory, costanti e funzioni di utilità nelle moderne applicazioni Android scritte in Kotlin. Gli sviluppatori lo scelgono rispetto alle classi di utilità con funzioni a livello di pacchetto perché companion object mantiene una connessione logica con la classe proprietaria.
Utilizza companion object per raggruppare il contesto statico — costanti, factory, metodi di supporto che appartengono logicamente alla classe ma non richiedono un'istanza.
La sintassi di base di companion object è semplice: la parola chiave companion viene posta prima della dichiarazione object all'interno della classe. Se non viene specificato un nome, l'oggetto riceve il nome Companion, a cui si può accedere esplicitamente o implicitamente.
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")
Nell'esempio sopra, TABLE_NAME e create() sono accessibili tramite User.TABLE_NAME e User.create(). Nota il modificatore const per le costanti primitive — garantisce l'inserimento del valore in fase di compilazione, simile a Java static final per primitivi e String.
Se un companion object non ha nome, è possibile accedervi tramite il nome automatico Companion: User.Companion.TABLE_NAME. In pratica questo è raramente necessario, ma utile quando si chiama da codice Java o quando si utilizza la riflessione.
// Both calls are equivalent
User.create("Bob")
User.Companion.create("Bob")
// Getting reference to companion object
val companion: User.Companion = User
Un companion object può avere un nome, il che migliora la leggibilità del codice e permette di accedervi con un nome significativo. Factory è il nome più comune per un companion object usato come factory.
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 nominato aiuta a documentare lo scopo dei membri statici. Nel refactoring, il nome rende il codice auto-documentante — lo sviluppatore vede immediatamente che Factory crea istanze di HttpResponse.
Una classe può contenere un solo companion object. Questa è una differenza fondamentale rispetto a Java, dove puoi dichiarare qualsiasi numero di campi e metodi statici senza raggruppamento. Se hai bisogno di più gruppi logici di membri statici, utilizza dichiarazioni object annidate senza companion.
Secondo la documentazione ufficiale di Kotlin (Kotlin Docs, 2025), la limitazione di un solo companion object è motivata dalla progettazione del linguaggio: i membri statici devono essere strettamente accoppiati con la classe e un oggetto compagno fornisce un confine chiaro per questa connessione. Se ci sono troppi membri statici, è un segnale per rifattorizzare la classe.
In Kotlin, anche le interfacce possono contenere un companion object. Ciò consente di definire metodi statici e costanti direttamente all'interno di un'interfaccia, cosa non possibile in Java. Questo approccio viene spesso utilizzato per dichiarare costanti relative all'interfaccia o metodi factory.
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)
}
}
In questo esempio, BASE_URL, TIMEOUT_MS e create() appartengono all'interfaccia ApiService, non alle sue implementazioni. Questo è comodo: tutte le costanti e la logica factory per creare un'istanza del servizio API si trovano in un unico posto — all'interno dell'interfaccia stessa.
Le implementazioni dell'interfaccia non ereditano i membri del companion object — vengono chiamati solo tramite il nome dell'interfaccia: ApiService.BASE_URL.
Quando si chiamano metodi di companion object da codice Java, per impostazione predefinita sono accessibili come metodi della classe interna Companion, non come metodi statici della classe esterna. Per esportarli come veri metodi e campi statici per Java, usa l'annotazione @JvmStatic per i metodi e @JvmField per i campi.
| Annotazione | Applicazione | Risultato in Java |
|---|---|---|
| @JvmStatic | Metodi di companion object | Metodo statico: ClassName.method() |
| @JvmField | Campi di companion object | Campo statico: ClassName.field |
| const | Primitivi e String | Costante inline: il compilatore sostituisce il valore |
| senza annotazione | Metodi/campi predefiniti | 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
Si raccomanda di usare @JvmStatic per tutti i metodi pubblici di companion object che devono essere accessibili da codice Java. const si applica solo ai tipi primitivi e String — per altri tipi usa @JvmField.
Nei progetti reali, companion object viene utilizzato per diversi scenari standard. I metodi factory sono il modello più comune: invece di molteplici costruttori con firme diverse, si utilizzano metodi factory nominati nel 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 in NetworkResult fornisce un metodo factory loading() che crea un'istanza Loading con il tipo generico corretto. Senza questo metodo, dovresti creare un'istanza direttamente tramite Loading(), che espone l'implementazione interna.
companion object viene spesso utilizzato per memorizzare costanti specifiche della classe. Le costanti dichiarate con const val vengono inserite in fase di compilazione, offrendo un overhead zero in fase di esecuzione.
class UserRepository {
companion object {
private const val TAG = "UserRepository"
private const val CACHE_SIZE = 100
private const val DEFAULT_PAGE_SIZE = 20
}
}
Domande Frequenti
Un object normale è un singleton a tutti gli effetti che esiste indipendentemente dalla classe. companion object è un object all'interno di una classe i cui membri vengono chiamati tramite il nome della classe esterna. In bytecode, un companion object diventa una classe annidata statica, mentre un object normale diventa una classe singleton autonoma.
No, companion object non viene ereditato dalle sottoclassi. Se la classe B estende la classe A, allora B.method() non chiamerà il metodo dal companion object della classe A — devi chiamare A.method(). Questo corrisponde al comportamento dei metodi statici in Java.
Per impostazione predefinita — tramite ClassName.Companion.method(). Per chiamarlo come metodo statico, aggiungi @JvmStatic al metodo del companion object. Per i campi, usa @JvmField o dichiarali con const val per i primitivi.
Gli sviluppatori di Kotlin hanno deciso di abbandonare static in favore di companion object per un lavoro uniforme con gli oggetti. static in Java viola i principi OOP perché i metodi statici non sono legati a un'istanza. companion object è un oggetto di prima classe che può essere passato, esteso con funzioni di estensione e implementare interfacce.
Utilizzando const val per primitivi e String — overhead zero, il valore viene inserito nel bytecode. Per i metodi, non c'è overhead a meno che il metodo non sia inline. Nella maggior parte dei casi, companion object non crea alcun impatto misurabile sulle prestazioni. Usa @JvmStatic solo per API pubbliche chiamate da Java.
Riepilogo
Svilupperemo un'applicazione mobile chiavi in mano
IT Sectr crea applicazioni iOS e Android per startup e aziende dal 2017. Ti consulteremo e ti proporremo la soluzione migliore.
Leggi anche