data class in Kotlin – copy(), Destrukturierung und warum ich sie ständig nutze
Ein `data class` ist mehr als ein Container für Felder. Du bekommst equals(), hashCode(), toString(), copy() und Destrukturierung geschenkt – und arbeitest damit sauber mit unveränderlichen Objekten. Ich zeige dir die Teile, die im Alltag wirklich zählen.
In fast jeder App, die ich baue, gibt es kleine Datenhalter: ein Nutzerprofil, ein Warenkorb-Eintrag, ein UI-Zustand. Dafür schreibe ich fast nie eine normale Klasse, sondern eine data class – die schenkt mir jede Menge Boilerplate, den ich sonst von Hand tippen (und pflegen) müsste.
Was du geschenkt bekommst
data class User(val name: String, val age: Int)
val a = User("Mia", 30)
val b = User("Mia", 30)
println(a) // User(name=Mia, age=30) ← lesbares toString()
println(a == b) // true ← equals() vergleicht die Werte
println(a.hashCode() == b.hashCode()) // true ← passend zu equals()Bei einer normalen Klasse wäre a == b false (Referenzvergleich). Eine data class vergleicht dagegen inhaltlich – genau das, was man bei Daten fast immer will. Deshalb funktionieren sie auch als Set-Elemente oder Map-Schlüssel korrekt.
copy() – ändern, ohne das Original anzufassen
Der Teil, den ich am häufigsten brauche: Ich halte meine Objekte unveränderlich (val) und erzeuge bei einer Änderung eine neue Kopie mit copy().
val user = User("Mia", 30)
val geburtstag = user.copy(age = 31) // nur age wird ersetzt
println(user) // User(name=Mia, age=30) ← unverändert
println(geburtstag) // User(name=Mia, age=31)Das ist die Grundlage für sauberes State-Management (etwa in einem ViewModel mit Compose): Der alte Zustand bleibt, du produzierst einen neuen. Kein versehentliches Mutieren von Objekten, die woanders noch benutzt werden.
Destrukturierung – Felder direkt entpacken
val (name, age) = user
println("$name ist $age") // Mia ist 30Praktisch in Schleifen über Paare oder Map-Einträge:
for ((id, punkte) in scores) {
println("Spieler $id hat $punkte Punkte")
}Die Reihenfolge zählt: Destrukturierung geht nach der Position der Konstruktor-Parameter, nicht nach dem Namen. Ordnest du die Parameter um, ändert sich also auch das Entpacken.
Eine Warnung zum Schluss
equals(), copy() & Co. betrachten nur die Properties im primären Konstruktor. Ein Feld, das du erst im Klassenrumpf deklarierst (val extra = …), fließt nicht in equals()/copy() ein. Wenn ein Wert zur Identität gehört, muss er in den Konstruktor.
Wenn du eine Android-App in Kotlin planst und Wert auf sauberen, wartbaren Code legst, setze ich das gern für dich um – schreib mir über bymw.de.
Quellen
Du brauchst mehr als ein Snippet?
Ich entwickle Android-Apps in Kotlin und moderne Websites für Selbstständige und kleine Unternehmen — von der ersten Idee bis zum Release.
Projekt anfragen →Verwandte Snippets
Nach mehreren Kriterien sortieren – sortedWith, compareBy und thenBy
Erst nach Priorität, dann nach Datum, und die ohne Termin ganz nach hinten: Solche Sortierungen werden mit einem handgeschriebenen Comparator schnell unleserlich. Kotlin hat dafür eine Baukastenschreibweise.
Room-Migrationen – das Schema ändern, ohne Nutzerdaten zu verlieren
Eine neue Spalte in der Entity, und beim nächsten Start ist der Spielstand weg – weil fallbackToDestructiveMigration die Datenbank einfach löscht. So machst du es richtig, inklusive Test.
Fehler in Coroutinen – coroutineScope, supervisorScope und wer wen mitreißt
Eine von fünf parallelen Abfragen scheitert – und plötzlich sind alle abgebrochen. Das ist kein Bug, sondern strukturierte Nebenläufigkeit. Wer den Unterschied zwischen coroutineScope und supervisorScope kennt, steuert das bewusst.