value class in Kotlin – typsichere IDs ohne Extra-Kosten zur Laufzeit
Ein `String` für die Nutzer-ID, ein `String` für die Bestell-ID – und schon vertauschst du sie irgendwann. Mit `@JvmInline value class` bekommst du echte, unverwechselbare Typen, die zur Laufzeit trotzdem nur der nackte Wert sind. Kein Wrapper-Objekt, keine Bremse.
Kennst du das? Eine Funktion nimmt userId: String und orderId: String. Beide sind String, der Compiler nickt alles ab – und irgendwann übergibst du sie in der falschen Reihenfolge. Der Fehler fällt erst zur Laufzeit auf, im schlimmsten Fall bei echten Nutzern. Kotlins value class löst genau das: Du machst aus einem simplen Wert einen eigenen Typ, den man nicht mehr verwechseln kann – und zahlst dafür zur Laufzeit nichts.
Das Problem: alles ist ein String
fun ladeBestellung(userId: String, orderId: String) { /* ... */ }
val user = "u_42"
val order = "o_7"
ladeBestellung(order, user) // vertauscht – der Compiler merkt NICHTSDie Lösung: value class
@JvmInline value class UserId(val raw: String)
@JvmInline value class OrderId(val raw: String)
fun ladeBestellung(userId: UserId, orderId: OrderId) { /* ... */ }
ladeBestellung(OrderId("o_7"), UserId("u_42"))
// Fehler beim KOMPILIEREN: Type mismatch – nicht erst zur LaufzeitUserId und OrderId sind jetzt verschiedene Typen. Die Reihenfolge zu vertauschen, wird zum Compiler-Fehler statt zum Support-Ticket. Das Schöne: Ein value class mit einer einzigen Property wird vom Compiler, wo immer möglich, wieder auf den nackten Wert eingedampft (hier also auf den String). Es entsteht kein zusätzliches Objekt auf dem Heap – anders als bei einer normalen data class. Du bekommst also Sicherheit geschenkt, ohne Speicher oder Tempo zu opfern.
Auch für Werte mit Einheit – und mit eigener Logik
Der Trick trägt weiter als IDs. Überall, wo eine nackte Zahl eine Bedeutung hat, verhindert ein eigener Typ Einheiten-Verwechslungen. Ein value class darf auch Methoden und berechnete Properties haben:
@JvmInline value class Cents(val value: Long) {
val euros: Double get() = value / 100.0
operator fun plus(other: Cents) = Cents(value + other.value)
override fun toString() = "%.2f €".format(euros)
}
val preis = Cents(1990) + Cents(500)
println(preis) // 24,95 €Damit kann dir niemand mehr aus Versehen Euro und Cent addieren.
Zwei Dinge, die du wissen solltest
- Sobald der Typ „verpackt" gebraucht wird, entsteht doch ein Objekt. Steckst du einen
Cents-Wert in eineList<Cents>, machst ihnnullable(Cents?) oder gibst ihn als generischen Typ weiter, muss Kotlin ihn boxen. Für den heißen Pfad ist das gut zu wissen; im Normalfall ist es egal. init-Block für Validierung nutzen. So sind ungültige Werte gar nicht erst baubar:
@JvmInline value class Email(val raw: String) {
init { require("@" in raw) { "Keine gültige E-Mail: $raw" } }
}Ich nutze value class in fast jedem Android-Projekt für IDs und Geldbeträge – das räumt eine ganze Klasse dummer Fehler ab, bevor sie überhaupt entstehen. Wenn du eine saubere, wartbare App in Kotlin und Jetpack Compose brauchst, 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.