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.
Eine Aufgabenliste soll so erscheinen: offene zuerst, darin die dringendste Priorität oben, bei gleicher Priorität das ältere Fälligkeitsdatum zuerst, und Aufgaben ohne Datum ganz unten. Von Hand wird daraus ein compareTo-Knäuel mit vier if. Kotlin baut den Vergleich stattdessen aus Bausteinen.
Die Grundform
val sortiert = aufgaben.sortedWith(
compareBy<Aufgabe> { it.erledigt } // false vor true
.thenByDescending { it.prioritaet } // hohe Priorität zuerst
.thenBy(nullsLast()) { it.faellig } // ohne Datum ganz nach hinten
.thenBy { it.titel } // stabiler Endanschlag
)Das liest sich wie die Anforderung. compareBy startet, thenBy verfeinert, thenByDescending dreht nur diesen einen Schritt um.
nullsLast und nullsFirst
val nachTermin = termine.sortedWith(compareBy(nullsLast()) { it.datum })Ohne diese Angabe müsstest du vorher partitionieren oder mit einem Ersatzwert arbeiten – beides fehleranfällig. nullsLast() (und nullsFirst()) erzeugen einen Comparator, der die Lücken bewusst einsortiert.
Groß-/Kleinschreibung und Umlaute
// Einfach, aber ASCII-naiv: "Äpfel" landet hinter "Zwetschge"
val a = namen.sortedBy { it.lowercase() }
// Sprachlich richtig:
val collator = java.text.Collator.getInstance(java.util.Locale.GERMAN)
val b = namen.sortedWith(compareBy(collator) { it })Für Listen, die Menschen lesen, ist der Collator der Unterschied zwischen „sortiert" und „richtig sortiert".
In-Place, absteigend, und der Blick auf die Kosten
liste.sortWith(compareBy { it.name }) // ändert eine MutableList
val top = werte.sortedDescending().take(10)
// Besser, wenn du nur die Top 10 brauchst:
val top10 = werte.asSequence().sortedDescending().take(10).toList()sortedBy und Verwandte geben immer eine neue Liste zurück; sortBy/sortWith sortieren an Ort und Stelle. Und: Kotlins Sortierung ist stabil – Einträge mit gleichem Schlüssel behalten ihre Reihenfolge. Das ist der Grund, warum man mehrstufig auch durch nacheinander ausgeführte Sortierungen erreichen kann, es mit thenBy aber deutlicher hinschreibt.
Eigene Klassen vergleichbar machen
data class Version(val major: Int, val minor: Int, val patch: Int) : Comparable<Version> {
override fun compareTo(other: Version) =
compareValuesBy(this, other, { it.major }, { it.minor }, { it.patch })
}
listOf(Version(1, 2, 0), Version(1, 10, 3)).sorted() // 1.2.0, dann 1.10.3compareValuesBy ist die Einmal-Variante von compareBy – praktisch genau für compareTo.
Fallstrick
Ein Comparator muss konsistent sein: Wenn a vor b und b vor c kommt, muss a vor c kommen. Vergleiche mit Zufall, mit der aktuellen Uhrzeit oder mit Double.NaN verletzen das, und die Sortierung kann mit einer IllegalArgumentException abbrechen. Und noch etwas aus der Praxis: Sortiere nicht in der Zeichenfunktion einer Liste – in Compose gehört die sortierte Liste in den Zustand (oder hinter ein remember), sonst rechnest du sie bei jedem Neuzeichnen neu.
Wenn Listen in deiner App „irgendwie komisch" sortiert sind, ist die Ursache fast immer hier zu finden. Ich helfe gern: 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
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.
stateIn und shareIn – aus einem kalten Flow wird geteilter Zustand
Jeder Sammler eines kalten Flows startet die Arbeit neu – zwei Beobachter, zwei Datenbankabfragen. stateIn und shareIn machen daraus einen Strom, den sich alle teilen, samt aktuellem Wert und Abschalten bei Inaktivität.