Android Coden
Android 5 min lesen

Gleichheit und Identität in Kotlin

Du lernst, wann Kotlin Werte nach Inhalt vergleicht und wann zwei Referenzen wirklich dasselbe Objekt meinen.

Wenn du Android-Apps mit Kotlin baust, vergleichst du ständig Dinge: Eingaben, UI-State, Daten aus einer API, Items in Listen oder erwartete Werte in Tests. Dabei musst du unterscheiden, ob zwei Werte denselben Inhalt haben oder ob zwei Variablen auf exakt dasselbe Objekt im Speicher zeigen. Diese Unterscheidung wirkt klein, entscheidet aber oft darüber, ob dein Code korrekt reagiert, ob Tests stabil sind und ob Änderungen im UI sauber erkannt werden.

Was ist das?

Gleichheit bedeutet in Kotlin meistens: Zwei Werte gelten inhaltlich als gleich. Wenn zwei User-Objekte dieselbe id und denselben name haben, möchtest du sie fachlich oft als gleich behandeln, auch wenn sie an unterschiedlichen Stellen erzeugt wurden. Dafür nutzt du strukturelle Gleichheit mit ==. Kotlin ruft dabei intern equals() auf.

Identität bedeutet dagegen: Zwei Variablen zeigen auf dasselbe konkrete Objekt. Das prüfst du mit ===, also referenzieller Gleichheit. Zwei Objekte können denselben Inhalt haben und trotzdem nicht identisch sein. Für Anfänger ist das wichtigste mentale Modell: == fragt „gleicher Wert?“, === fragt „dieselbe Instanz?“.

Im Android-Alltag ist diese Trennung besonders wichtig, weil du häufig mit unveränderlichen Datenmodellen, ViewModels, Compose-State und Tests arbeitest. Ein Screen-State kann fachlich gleich bleiben, obwohl du eine neue Instanz erzeugst. Eine Liste kann ein neues Objekt sein, aber dieselben Einträge enthalten. Wenn du diese Fälle nicht sauber einordnest, entstehen schwer erkennbare Fehler bei UI-Aktualisierung, Assertions oder Zustandslogik.

Wie funktioniert es?

Kotlin unterscheidet zwischen struktureller und referenzieller Gleichheit. Der Operator == ist null-sicher und wird zu einem equals()-Vergleich übersetzt. a == b entspricht grob a?.equals(b) ?: (b === null). Du musst also nicht selbst vorher auf null prüfen.

Der Operator === prüft dagegen nur, ob beide Seiten dieselbe Referenz sind. Das brauchst du seltener. Typische Fälle sind Debugging, Caching, Singleton-Checks oder sehr bewusstes Arbeiten mit Objektidentität. Für normale Fachlogik ist === meist nicht die richtige Wahl.

Data Classes sind hier besonders wichtig. Wenn du eine Klasse mit data class definierst, erzeugt Kotlin automatisch equals(), hashCode(), toString() und copy() auf Basis der Properties im primären Konstruktor. Das passt sehr gut zu Android-Architektur, weil UI-State und DTOs häufig reine Daten tragen.

Wichtig ist aber die Grenze: Nur Properties im primären Konstruktor zählen für das automatisch generierte equals(). Properties im Klassenkörper werden nicht berücksichtigt. Außerdem solltest du bei Collections beachten, dass Listen strukturell verglichen werden: Zwei Listen mit gleichen Elementen in gleicher Reihenfolge gelten mit == als gleich, auch wenn es unterschiedliche Listenobjekte sind.

In der Praxis

Ein typisches Android-Beispiel ist ein UI-State für einen Profilbildschirm. Du möchtest testen, ob dein ViewModel den erwarteten Zustand liefert. Dafür ist strukturelle Gleichheit genau richtig.

data class ProfileUiState(
    val userId: String,
    val displayName: String,
    val isLoading: Boolean
)

val first = ProfileUiState(
    userId = "42",
    displayName = "Mina",
    isLoading = false
)

val second = ProfileUiState(
    userId = "42",
    displayName = "Mina",
    isLoading = false
)

println(first == second)  // true: gleicher Inhalt
println(first === second) // false: zwei verschiedene Instanzen

In einem Unit-Test würdest du deshalb eher so prüfen:

@Test
fun profileState_containsLoadedUser() {
    val actual = ProfileUiState("42", "Mina", isLoading = false)

    assertEquals(
        ProfileUiState("42", "Mina", isLoading = false),
        actual
    )
}

Diese Assertion ist robust, weil sie den fachlichen Zustand prüft. Sie interessiert sich nicht dafür, ob dieselbe Instanz wiederverwendet wurde. Das entspricht auch guter Android-Qualität: Tests sollten sichtbares Verhalten und fachlich relevante Zustände prüfen, nicht zufällige Implementierungsdetails.

Eine häufige Stolperfalle entsteht, wenn du eine normale Klasse statt einer Data Class verwendest:

class PlainUser(val id: String, val name: String)

val a = PlainUser("42", "Mina")
val b = PlainUser("42", "Mina")

println(a == b) // false, wenn equals nicht überschrieben wurde

Ohne eigene equals()-Implementierung erbt diese Klasse das Standardverhalten von Any, und das verhält sich praktisch wie ein Identitätsvergleich. Für Datenmodelle, UI-State und Testdaten ist daher oft data class die bessere Wahl.

Eine zweite Stolperfalle betrifft veränderbare Daten. Wenn du ein Objekt in eine HashSet-Collection legst und danach Properties änderst, die in equals() und hashCode() eingehen, kann die Collection das Objekt nicht mehr zuverlässig finden. Nutze für State-Modelle bevorzugt val und erzeuge neue Werte mit copy():

val loading = ProfileUiState("42", "Mina", isLoading = true)
val loaded = loading.copy(isLoading = false)

Als Entscheidungsregel kannst du dir merken: Für fachliche Vergleiche in App-Logik, Tests und UI-State nimm == und saubere Data Classes. Nutze === nur, wenn du ausdrücklich wissen willst, ob es dieselbe Instanz ist. In Code-Reviews lohnt sich die Frage: Prüft dieser Vergleich den Inhalt, den die App fachlich braucht, oder hängt er unbeabsichtigt an einer Objektinstanz?

Fazit

Gleichheit und Identität sind Grundlagen, die in professionellem Android-Code ständig mitlaufen. Wenn du ==, ===, equals() und Data Classes sauber trennst, werden deine Tests klarer, dein UI-State verständlicher und deine Architektur weniger fehleranfällig. Übe das gezielt: Schreibe zwei kleine Data Classes, vergleiche Instanzen im Debugger, ändere eine Property per copy() und ergänze Unit-Tests mit assertEquals. Prüfe danach in einem Code-Review bewusst, ob jeder Vergleich den fachlich richtigen Maßstab verwendet.

Quellen (4)
Redaktion

Geschrieben von

Redaktion

Das Redaktionsteam recherchiert und schreibt Artikel zu aktuellen Themen rund um Tech, Lifestyle und Ratgeber.