Warum gibt es in Kotlin kein static, aber ein companion object? Leistung und Logik verstehen
Wenn Sie von Java zu Kotlin wechseln, ist eine der ersten Fragen, die Sie auf StackOverflow stellen werden (und sie hat tatsächlich tausende Aufrufe gesammelt): „Warum verwenden wir companion object anstelle von Java static? Und verlangsamt das die Anwendung?“
Seien wir ehrlich: Als ich zum ersten Mal die Konstruktion companion object sah, dachte ich, das Kotlin-Team hätte einfach beschlossen, das Leben zu erschweren. Warum mehr Code schreiben, wenn man einfach static aus Java kopieren könnte? Aber nachdem ich tiefer gegraben habe, wurde mir klar: Das ist keine Laune, sondern eine durchdachte architektonische Entscheidung, die den Code sauberer, sicherer und … ja, manchmal sogar schneller macht.
In diesem Artikel übersetzen wir nicht nur eine Antwort von SO. Wir analysieren, wie companion object unter der Haube funktioniert, vergleichen die Leistung mit Java static und zeigen, wann man es verwendet und wann man besser einen anderen Weg wählt.
Was ist ein companion object in einfachen Worten?
In Java gehören statische Felder und Methoden zur Klasse, nicht zur Instanz. In Kotlin hat man sich vom Schlüsselwort static verabschiedet, weil es die OOP-Prinzipien verletzt (Statik kann nicht überschrieben werden, sie ist kein Teil des Objekts). Stattdessen bietet Kotlin Begleitobjekte (companion objects).
Stellen Sie sich vor, Sie haben eine Klasse User. Sie möchten eine Konstante MAX_AGE und eine Factory-Methode createGuest() speichern. In Java würden Sie schreiben:
public class User { public static final int MAX_AGE = 120; public static User createGuest() { return new User("guest", "1234"); }}In Kotlin sieht das so aus:
class User(private val name: String, private val password: String) { companion object { const val MAX_AGE = 120 fun createGuest() = User("guest", "1234") }}Beachten Sie: companion object ist nicht einfach ein „static-Block“. Es ist ein vollwertiges Objekt, das innerhalb Ihrer Klasse lebt. Es kann Schnittstellen, Erweiterungen und sogar einen eigenen Namen haben (wenn Sie ihn vergeben).
Wie funktioniert companion object unter der Haube? (Aus JVM-Sicht)
Um die Leistung zu verstehen, werfen wir einen Blick in den Bytecode. Wenn Sie ein companion object schreiben, macht der Kotlin-Compiler Folgendes:
- Er erstellt eine innere statische Klasse mit dem Namen
Companion(oder Ihrem benutzerdefinierten Namen). - Er platziert alle Felder und Methoden aus dem companion object in diese Klasse.
- Wenn Sie die Annotation
@JvmStaticverwenden, generiert der Compiler zusätzlich statische Wrapper-Methoden in der Klasse selbst.
Beispiel mit der Annotation @JvmStatic:
class User { companion object { @JvmStatic fun createGuest() = User() }}Jetzt können Sie aus Java User.createGuest() wie eine normale statische Methode aufrufen. Ohne diese Annotation müssten Sie User.Companion.createGuest() schreiben.
Fazit zur Leistung: Wenn Sie nur mit Kotlin arbeiten, gibt es keine zusätzlichen Kosten. Wenn Sie aus Java aufrufen, gibt es eine zusätzliche Indirektionsebene (über das Companion-Objekt). Aber der JIT-Compiler der JVM optimiert solche Aufrufe hervorragend, sodass der Geschwindigkeitsunterschied gegen Null geht.
Leistung: companion object vs Java static
Lassen Sie uns den größten Mythos entkräften: companion object verlangsamt NICHT. Ich habe einige Mikro-Benchmarks durchgeführt (mit Kotlin 1.9, JVM 17), und hier ist das Ergebnis: