companion object — het mechanisme van Kotlin dat het sleutelwoord static uit Java vervangt voor het declareren van statische leden van een klasse. Kotlin heeft geen ingebouwde static — in plaats daarvan wordt een object-declaratie met de modificator companion binnen de klasse gebruikt. Volgens JetBrains, 2025 maakt companion object het mogelijk om methoden en eigenschappen via de klassenaam aan te roepen, wat ze de volledige equivalent maakt van statische leden in het Java-ecosysteem.
Belangrijkste punten
companion object — is een speciale variant van object declaration in Kotlin, gemarkeerd met het sleutelwoord companion. Het maakt het mogelijk leden te declareren die bij de klasse horen, niet bij de instanties — dus statische leden in Java-terminologie. In tegenstelling tot Java, waar static een modificator is voor individuele velden en methoden, worden in Kotlin statische leden gegroepeerd binnen één companion-object.
Elke klasse in Kotlin kan precies één companion object bevatten. Leden van dit object zijn toegankelijk via de klassenaam zonder een instantie te maken: MyClass.method(). Vanuit bytecode-perspectief wordt companion object gecompileerd tot een aparte innerlijke klasse, en de methoden worden statische methoden van de buitenste klasse bij gebruik van de @JvmStatic-annotatie.
Volgens Google I/O 2024 is companion object het belangrijkste patroon geworden voor fabrieksmethoden, constanten en hulpfuncties in moderne Android-applicaties in Kotlin. Ontwikkelaars kiezen het boven hulpklassen met pakketfuncties, omdat companion object de logische band met de eigenaar-klasse behoudt.
Gebruik companion object voor het groeperen van statische context — constanten, fabrieken, hulpmethoden die logisch bij de klasse horen maar geen instantie vereisen.
De basissyntaxis van companion object is eenvoudig: het sleutelwoord companion wordt voor de object-declaratie binnen de klasse geplaatst. Als er geen naam wordt opgegeven, krijgt het object de naam Companion, waarnaar expliciet of impliciet kan worden verwezen.
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 }
}
// Toegang via klassenaam
val table = User.TABLE_NAME
val user = User.create("Alice")
In bovenstaand voorbeeld zijn TABLE_NAME en create() toegankelijk via User.TABLE_NAME en User.create(). Let op de modificator const voor primitieve constanten — deze garandeert het inlinen van de waarde tijdens compilatie, analoog aan Java static final voor primitives en String.
Als companion object geen naam heeft, kan het worden benaderd via de automatische naam Companion: User.Companion.TABLE_NAME. In de praktijk is dit zelden nodig, maar handig bij aanroep vanuit Java-code of bij gebruik van reflectie.
// Beide aanroepen zijn equivalent
User.create("Bob")
User.Companion.create("Bob")
// Referentie naar companion object verkrijgen
val companion: User.Companion = User
companion object kan een naam hebben, wat de leesbaarheid van de code verbetert en het mogelijk maakt het object met een begrijpelijke naam aan te spreken. Factory — de meest voorkomende naam voor een companion object dat als fabriek wordt gebruikt.
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")
}
}
// Toegang via Factory-naam
val response = HttpResponse.Factory.ok("data")
Een benoemde companion object helpt bij het documenteren van het doel van statische leden. Bij refactoring maakt de naam de code zelfdocumenterend — de ontwikkelaar ziet direct dat Factory instanties van HttpResponse maakt.
Een klasse kan slechts één companion object bevatten. Dit is een fundamenteel verschil met Java, waar elk aantal statische velden en methoden zonder groepering kan worden gedeclareerd. Als meerdere logische groepen statische leden nodig zijn, gebruik dan geneste object-declaraties zonder companion.
Volgens de officiële Kotlin-documentatie (Kotlin Docs, 2025) is de beperking tot één companion object gemotiveerd door het taalontwerp: statische leden moeten nauw verbonden zijn met de klasse, en één companion-object zorgt voor een duidelijke grens van deze verbinding. Als er te veel statische leden zijn — is dat een signaal voor refactoring van de klasse.
In Kotlin kunnen ook interfaces een companion object bevatten. Dit maakt het mogelijk om statische methoden en constanten direct binnen de interface te definiëren, wat niet mogelijk is in Java. Deze benadering wordt vaak gebruikt voor het declareren van constanten die aan de interface zijn gekoppeld, of fabrieksmethoden.
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 dit voorbeeld behoren BASE_URL, TIMEOUT_MS en create() tot de interface ApiService, niet tot de implementaties ervan. Dit is handig: alle constanten en fabriekslogica voor het maken van een API-service-instantie bevinden zich op één plek — binnen de interface zelf.
Implementaties van de interface erven de leden van companion object niet — ze worden alleen via de interfacenaam aangeroepen: ApiService.BASE_URL.
Bij het aanroepen van companion object-methoden vanuit Java-code zijn ze standaard beschikbaar als methoden van de geneste klasse Companion, niet als statische methoden van de buitenste klasse. Om ze te exporteren als echte static-methoden en -velden voor Java, gebruik de annotatie @JvmStatic voor methoden en @JvmField voor velden.
| Annotatie | Toepassing | Resultaat in Java |
|---|---|---|
| @JvmStatic | Companion object-methoden | Statische methode: ClassName.method() |
| @JvmField | Companion object-velden | Statisch veld: ClassName.field |
| const | Primitives en String | Inline-constante: compiler substitueert waarde |
| geen annotatie | Standaard methoden/velden | 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"
}
}
// Aangeroepen in Java als MathUtils.square(5), MathUtils.TAG
Het gebruik van @JvmStatic wordt aanbevolen voor alle openbare methoden van companion object die toegankelijk moeten zijn vanuit Java-code. const wordt alleen toegepast op primitieve typen en String — voor andere typen gebruik @JvmField.
In echte projecten wordt companion object gebruikt voor verschillende standaard scenario's. Fabrieksmethoden — het meest voorkomende patroon: in plaats van meerdere constructors met verschillende handtekeningen worden benoemde fabrieksmethoden in companion object gebruikt.
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 biedt de fabrieksmethode loading() die een Loading-instantie maakt met het juiste generic-type. Zonder deze methode zou u de instantie direct via Loading() moeten maken, wat de interne implementatie blootlegt.
Vaak wordt companion object gebruikt voor het opslaan van klassenspecifieke constanten. Constanten gedeclareerd met const val worden tijdens compilatie geïnlined, wat nul overhead in runtime oplevert.
class UserRepository {
companion object {
private const val TAG = "UserRepository"
private const val CACHE_SIZE = 100
private const val DEFAULT_PAGE_SIZE = 20
}
}
Veelgestelde vragen
ordinary object — is een volwaardige singleton die onafhankelijk van de klasse bestaat. companion object — is een object binnen de klasse waarvan de leden worden aangeroepen via de naam van de buitenste klasse. In bytecode wordt companion object een statische geneste klasse, en een gewoon object wordt een autonome singleton-klasse.
Nee, companion object wordt niet overgeërfd door subklassen. Als klasse B overerft van klasse A, roept B.method() geen methode aan uit companion object van klasse A — u moet via A.method() benaderen. Dit komt overeen met het gedrag van statische methoden in Java.
Standaard — via ClassName.Companion.method(). Om als statische methode aan te roepen, voeg @JvmStatic toe aan de companion object-methode. Voor velden gebruik @JvmField of declareer ze met const val voor primitieven.
De ontwikkelaars van Kotlin hebben besloten static te laten vallen ten gunste van companion object voor uniform werken met objecten. static in Java schendt de OOP-principes omdat statische methoden niet aan een instantie zijn gebonden. companion object — is een eersteklas object dat kan worden doorgegeven, uitgebreid met extensiefuncties en interfaces kan implementeren.
Bij gebruik van const val voor primitieven en String — nul overhead, de waarde wordt in bytecode geïnlined. Voor methoden is er geen overhead als de methode niet inline is. In de meeste gevallen heeft companion object geen meetbare invloed op de prestaties. Gebruik @JvmStatic alleen voor de publieke API die vanuit Java wordt aangeroepen.
Samenvatting
We ontwikkelen een mobiele applicatie turnkey
IT Sectr creëert sinds 2017 iOS- en Android-applicaties voor startups en bedrijven. We adviseren u en stellen de beste oplossing voor.
Lees ook