scalatutorial.de

Stufe 5Collections · Lektion 28 von 34

Array und veränderliche Collections

Aktualisiert am 24. September 20263 Min. LesezeitSchritt 4/4 dieser Stufe

Unveränderliche Collections sind in Scala der Normalfall. Für Schnittstellen zu Java, für rechenintensive Schleifen und für das schrittweise Aufbauen großer Datenmengen gibt es aber veränderliche Gegenstücke: Array, ArrayBuffer und ListBuffer.

Array

Ein Scala-Array ist ein ganz normales Java-Array. Es hat eine feste Länge, die Elemente lassen sich aber ändern:

 Scala 3
val zahlen = Array(5, 3, 8, 1)
zahlen(0) = 50               // Element ändern
zahlen.length                // 4
zahlen.sorted                // neues Array: Array(1, 3, 8, 50)
zahlen.mkString(", ")        // "50, 3, 8, 1"

val leer = Array.ofDim[Double](3)       // Array(0.0, 0.0, 0.0)
val matrix = Array.ofDim[Int](3, 3)     // 3 × 3, alle 0
matrix(1)(2) = 7

Auf Arrays funktionieren alle Methoden, die Sie von List kennen – map, filter, foldLeft und so weiter. Sie liefern jeweils ein neues Array.

Arrays brauchen Sie vor allem, wenn eine Java-Bibliothek eines erwartet oder liefert, wie args: Array[String] in der klassischen main-Methode, und in rechenintensivem Code, der von der kompakten Speicherung profitiert: Ein Array[Int] speichert echte primitive Zahlen, ohne jedes Element als Objekt zu verpacken.

ArrayBuffer: ein Array, das wachsen kann

 Scala 3
import scala.collection.mutable.ArrayBuffer

val puffer = ArrayBuffer[String]()
puffer += "eins"
puffer ++= List("zwei", "drei")
puffer.insert(0, "start")
puffer.remove(1)             // entfernt "eins"
puffer(0)                    // "start"
puffer.toList                // List(start, zwei, drei)

Ein ArrayBuffer verhält sich wie Javas ArrayList: schneller Zugriff per Index, schnelles Anhängen am Ende. Einfügen am Anfang oder in der Mitte ist dagegen teuer, weil alle folgenden Elemente verschoben werden.

ListBuffer: Listen effizient aufbauen

ListBuffer ist darauf spezialisiert, eine Liste Stück für Stück aufzubauen und am Ende in eine unveränderliche List zu verwandeln:

 Scala 3
import scala.collection.mutable.ListBuffer

def primzahlenBis(n: Int): List[Int] =
  val ergebnis = ListBuffer[Int]()
  for k <- 2 to n do
    if ergebnis.forall(p => k % p != 0) then ergebnis += k
  ergebnis.toList

primzahlenBis(30)   // List(2, 3, 5, 7, 11, 13, 17, 19, 23, 29)

toList kostet bei einem ListBuffer nur einen konstanten Aufwand – er wird dabei nicht kopiert. Genau dafür ist er gemacht.

Veränderlich lokal, unveränderlich nach außen

Die übliche Faustregel lautet: Veränderliche Collections dürfen innerhalb einer Methode verwendet werden, wenn das die Lösung einfacher oder schneller macht. Nach außen gibt die Methode aber eine unveränderliche Sammlung zurück – so wie primzahlenBis oben. Für Aufrufer ist die Funktion damit genauso sicher, als wäre sie rein funktional geschrieben.

Gefährlich wird es, wenn veränderliche Collections über Methodengrenzen oder gar zwischen Threads geteilt werden. Dann kann jede Stelle im Programm den Inhalt verändern, und Fehler werden schwer nachvollziehbar.

Umwandeln zwischen den Welten

 Scala 3
val a = Array(1, 2, 3)
a.toList          // List(1, 2, 3)
a.toVector        // Vector(1, 2, 3)
a.to(ArrayBuffer) // ArrayBuffer(1, 2, 3)

List(1, 2, 3).toArray   // Array(1, 2, 3)

Das allgemeine to(…) nimmt als Argument das Begleitobjekt der gewünschten Collection und funktioniert mit jedem Typ.

Welche veränderliche Collection wofür?

BedarfWerkzeug
feste Größe, Java-Schnittstelle, ZahlenkolonnenArray
wachsende Liste mit IndexzugriffArrayBuffer
Liste Stück für Stück aufbauen, am Ende ListListBuffer
Warteschlange (vorne raus, hinten rein)mutable.Queue
Stapelmutable.Stack
Menge bzw. Zuordnungmutable.Set, mutable.Map

Passt dazu