Einleitung: Warum die Verwirrung um Sternchen und Any völlig normal ist
\n\nWenn Sie seit mindestens ein paar Wochen mit Kotlin arbeiten, sind Sie bestimmt schon über Generics gestolpert. Und wenn Sie einen Blick in den Quellcode der Standardbibliothek geworfen oder etwas wie List<*> geschrieben haben, haben Sie sich vielleicht gefragt: „Warum nicht einfach List<Any> schreiben? Das Sternchen bedeutet doch so etwas wie „beliebiger Typ“, oder?“
Diese Frage ist ein echter Dauerbrenner auf Stack Overflow (tausende Aufrufe). Kein Wunder: Auf den ersten Blick scheinen * und Any austauschbar. Aber in Wirklichkeit klafft eine große Lücke zwischen ihnen – und wenn man diese übersieht, kann man Code schreiben, der nicht kompiliert oder, noch schlimmer, stillschweigend zur Laufzeit abstürzt.
In diesem Artikel klären wir ein für alle Mal, wie sich * (Star Projection) und Any in Kotlin Generics unterscheiden. Sie erfahren, wann Sie welches verwenden sollten, wie Varianz funktioniert und warum das Sternchen nicht „Any“, sondern „Ich weiß nicht, was da drin ist, aber es ist mir egal“ bedeutet. Los geht’s!
Kurz zu Generics: Warum wir sie überhaupt brauchen
\n\nGenerics (Verallgemeinerungen) ermöglichen es uns, Klassen und Funktionen zu schreiben, die mit verschiedenen Typen arbeiten, ohne dabei die Typsicherheit zu verlieren. Anstatt eine separate Klasse für Int, eine für String und eine für User zu schreiben, erstellen wir eine einzige Klasse Box<T>.
Aber was tun, wenn wir eine Funktion schreiben wollen, die jede Box akzeptiert, unabhängig davon, was darin ist? Hier kommen unsere Helden ins Spiel: * und Any.
Any ist ein konkreter Typ (fast wie Object)
\n\nIn Kotlin ist Any die Wurzel der Typhierarchie für alle Non-Null-Typen. Wenn Sie List<Any> schreiben, bedeutet das: „Eine Liste, die Elemente vom Typ Any enthält“. Sie können also Int, String, User – alles Mögliche – hineinstecken, weil alle diese Typen von Any erben.
Wichtiger Punkt: List<Any> ist ein konkreter parametrisierter Typ. Der Compiler weiß, dass der Elementtyp Any ist.
val listOfAny: MutableList<Any> = mutableListOf(1, "hallo", 3.14)\nlistOfAny.add(42) // OK\nlistOfAny.add("welt") // OK\n\n\nHier ist alles vorhersehbar: Wir haben explizit gesagt, dass die Liste mit Any arbeitet. Aber es gibt einen Haken: MutableList<Any> ist nicht dasselbe wie MutableList<String>. Sie können eine Liste von Strings nicht dort übergeben, wo eine Liste von Any erwartet wird (es sei denn, Sie verwenden Kovarianz, aber dazu später mehr).
Das Sternchen (*) bedeutet „Ich weiß es nicht, aber es ist mir egal“
\n\nDas Sternchen, auch Star Projection genannt, ist eine ganz andere Geschichte. Wenn Sie List<*> schreiben, teilen Sie dem Compiler mit: „Das ist eine Liste von irgendeinem Typ, aber ich weiß nicht, welcher es genau ist, und das ist mir auch egal.“ Es handelt sich um eine Projektion eines unbekannten Typs.
Der entscheidende Unterschied: * ist kein Typ, sondern ein Platzhalter (Wildcard). Er bedeutet, dass der Typ unbekannt ist, und der Compiler schränkt ein, was Sie mit einem solchen Objekt tun können.
fun printLength(list: List<*>) {\n // list.add(1) // Compiler-Fehler! Element kann nicht hinzugefügt werden\n println(list.size) // OK: Größe kann abgefragt werden\n for (item in list) {\n println(item) // OK: item hat den Typ Any?\n }\n}\n\n\nBeachten Sie: Wenn Sie Elemente aus einer List<*> lesen, haben diese den Typ Any? (weil die Liste null enthalten könnte). Sie können jedoch keine Elemente hinzufügen, da der Compiler nicht weiß, welcher Typ tatsächlich gespeichert wird. Dies schützt vor der Verletzung der Typsicherheit.
Der Hauptunterschied auf den Punkt gebracht
\n\n- \n
List<Any>– eine Liste, die Elemente vom TypAnyenthalten kann. Sie können Elemente lesen und schreiben (solange sie vom TypAnysind). \n List<*>– eine Liste von einem unbekannten Typ. Sie können Elemente nur lesen (alsAny?), aber keine neuen Elemente hinzufügen. \n
Das Sternchen ist also keine Abkürzung für Any, sondern ein Werkzeug, um mit unbekannten, aber typsicheren Generics umzugehen.