Teil 6.3: Typkonvertierungen

Es kommt oft vor, dass wir einen Typ haben aber für eine bestimmte Operation einen ganz anderen brauchen. So erwartet eine Methode z.B. einen Double-Wert, wir haben aber nur einen Int. In diesem Fall wäre es kein Problem, da Scala sogenannte `widening-operations` besitzt. D.h. alle Zahlentypen werden automatisch in einen Typ konvertiert, der den Zahlentyp aufnehmen kann.

Die Wichtigsten davon sind:

Int -> Long -> Float -> Double

Dass das funktioniert sehen wir hier:

scala> val l: Long = 12345
l: Long = 12345

scala> val f: Float = l
f: Float = 12345.0

scala> val d: Double = f
d: Double = 12345.0

Wenn wir mit anderen Typen arbeiten finden keine solchen automatischen Konvertierungen statt, wir müssen sie also von Hand vornehmen. Das geht in Scala aber denkbar einfach. Alle Konvertierungsmethoden fangen mit `to` an und enden mit dem Typ in den sie konvertiert werden sollen:

scala> val xs = Seq(1, 2, 3)
xs: Seq[Int] = List(1, 2, 3)

scala> xs.toList
res22: List[Int] = List(1, 2, 3)

scala> xs.toSet
res23: scala.collection.immutable.Set[Int] = Set(1, 2, 3)

scala> xs.toString
res24: String = List(1, 2, 3)

Natürlich können wir nicht jeden Typ in jeden konvertieren. Es ist z.B. nicht möglich eine Seq mit Int in eine Map zu wandeln. Was soll auch das Ergebnis sein? Die Map benötigt sowohl einen Schlüssel als auch einen dazugehörenden Wert:

scala> xs.toMap
<console>:10: error: Cannot prove that Int <:< (T, U).
              xs.toMap
                 ^

scala> val xs = Seq(1 -> "a", 2 -> "b", 3 -> "c")
xs: Seq[(Int, java.lang.String)] = List((1,a), (2,b), (3,c))

scala> xs.toMap
res26: scala.collection.immutable.Map[Int,java.lang.String] = Map(1 -> a, 2 -> b, 3 -> c)

Falls unsere Seq aber schon einen Tuple2 beinhaltet ist die Konvertierung gar kein Problem.

Neben den `to`-Methoden besteht auch noch die Möglichkeit mit Hilfe der `++`-Methode die Elemente einfach an eine andere Collection dranzuhängen:

scala> val xs = Seq(1 -> "a", 2 -> "b", 3 -> "c")
xs: Seq[(Int, java.lang.String)] = List((1,a), (2,b), (3,c))

scala> Set.empty ++ xs
res29: scala.collection.immutable.Set[(Int, java.lang.String)] = Set((1,a), (2,b), (3,c))

scala> Nil ++ xs
res30: List[(Int, java.lang.String)] = List((1,a), (2,b), (3,c))

scala> Map.empty ++ xs
res31: scala.collection.immutable.Map[Int,java.lang.String] = Map(1 -> a, 2 -> b, 3 -> c)

Die Methode `empty` erzeugt eine leere Collection, an die die Elemente angefügt werden können. Anstatt `empty` könnten wir auch einfach einen leeren Konstruktor aufrufen (z.B. `Set() ++ xs`). Wir müssen aber keinen leeren Konstruktor aufrufen sondern können ihm direkt unsere Collection übergeben:

scala> Set(xs)
res33: scala.collection.immutable.Set[Seq[(Int, java.lang.String)]] = Set(List((1,a), (2,b), (3,c)))

scala> Set(xs: _*)
res34: scala.collection.immutable.Set[(Int, java.lang.String)] = Set((1,a), (2,b), (3,c))

Wir erhalten zwei verschiedene Ausgaben. Beim ersten Mal erhalten wir ein Set, das eine List mit Tuple2 beinhaltet. Beim zweiten Mal erhalten wir ein Set, das die Tuple2 ohne eine weitere List beinhaltet. Das letztere Ergebnis ist das das wir haben wollen, aber was ist der Unterschied?
Nun, der Konstruktor von Set erwartet ein Objekt, das er in das Set einfügen soll. Er kann ja nicht wissen ob wir die Seq oder die Tuple2 aufnehmen wollen. Wir müssen es ihm also irgendwie mitteilen.
Dies erreichen wir mit dem am Anfang etwas ungewöhnlich ausschauenden `: _*`. Die Bedeutung des Punktes kennen wir bereits. Damit wird ein Typ an eine Variable gebunden. Die beiden anderen Zeichen sind uns dagegen noch unbekannt. Der Stern kennzeichnet in Scala `varargs`. Wir können damit festlegen, dass beliebig viele Argumente an eine Variable gebunden werden können:

scala> def deliverVarArgs(i: Int*) { println(i) }
deliverVarArgs: (i: Int*)Unit

scala> deliverVarArgs(1, 2, 3, 4)
WrappedArray(1, 2, 3, 4)

Der Compiler erzeugt für uns ein WrappedArray[Int], das an die Variable `i` gebunden wird.

Das letzte unbekannte Symbol ist der Unterstrich. Dieser kann in Scala viele Bedeutungen haben. Die Wichtigste ist, dass er einen Platzhalter bzw. ein Wildcard-Symbol darstellt. In Verbindung mit dem Stern bedeutet es wandle etwas in ein varargs-Argument um. Das „etwas“ ist in diesem Fall die Variable `xs`. Wir können damit also angeben ob die Elemente der Seq oder die Seq selbst an den Konstruktor übergeben werden.

Zusammenfassend haben wir jetzt also drei Möglichkeiten kennen gelernt eine Collection in eine andere zu wandeln:

scala> Set.empty ++ xs
res38: scala.collection.immutable.Set[(Int, java.lang.String)] = Set((1,a), (2,b), (3,c))

scala> Set(xs: _*)
res39: scala.collection.immutable.Set[(Int, java.lang.String)] = Set((1,a), (2,b), (3,c))

scala> xs.toSet
res40: scala.collection.immutable.Set[(Int, java.lang.String)] = Set((1,a), (2,b), (3,c))

Welche ihr anwenden wollt bleibt vollkommen euch überlassen. Es gibt sogar noch weitere Möglichkeiten, da diese aber nur in Spezialfällen angewendet werden werde ich erst später darauf eingehen.

Casts

Manchmal wollen wir aber gar nicht konvertieren, sondern casten. Wir haben z.B. eine Implementierung und wollen aber nur mit der Schnittstelle arbeiten. Oder aber es ist genau anders herum – wir haben die Schnittstelle wollen aber an die Implementierung. In Scala gibt es dafür `asInstanceOf`:

scala> val xs = List(1, 2, 3)
xs: List[Int] = List(1, 2, 3)

scala> xs.asInstanceOf[Seq[Int]]
res42: Seq[Int] = List(1, 2, 3)

Bein Upcasten ist es aber auch ausreichend einer Variable einfach einen höheren Typ zu geben:

scala> val seq: Seq[Int] = xs
seq: Seq[Int] = List(1, 2, 3)

Beim Downcasten müssen wir aufpassen ob der Cast zur Laufzeit auch funktionieren wird. Prüfen können wir dies mit `isInstanceOf`:

scala> val xs = Seq(1, 2, 3)
xs: Seq[Int] = List(1, 2, 3)

scala> xs.isInstanceOf[List[Int]]
<console>:9: warning: non variable type-argument Int in type List[Int] is unchecked since it is eliminated by erasure
              xs.isInstanceOf[List[Int]]
                             ^
res0: Boolean = true

scala> xs.asInstanceOf[List[Int]]
res1: List[Int] = List(1, 2, 3)

Scala unterliegt leider den gleichen Einschränkungen was Generics betrifft wie Java auch: type erasure. Die Typprüfung kann also nicht prüfen ob die Liste tatsächlich aus lauter Ints besteht, wir sollten also gar nicht erst auf einen genauen Typ prüfen:

scala> xs.isInstanceOf[List[String]]
<console>:9: warning: non variable type-argument String in type List[String] is unchecked since it is eliminated by erasure
              xs.isInstanceOf[List[String]]
                             ^
res3: Boolean = true

scala> xs.isInstanceOf[List[_]]
res4: Boolean = true

Der Unterstrich steht hier wieder für einen unbekannten Typ oder ein Wildcard. Durch den Cast können wir den Typinferenz-Checker überlisten und ihm falsche Daten vorgaukeln. Versucht also wenn es nur irgendwie möglich ist einen parametisierten Cast zu vermeiden:

scala> val ys: List[String] = xs.asInstanceOf[List[String]] // don't do that!
ys: List[String] = List(1, 2, 3)

scala> val ys: List[Any] = xs.asInstanceOf[List[Any]] // ok
ys: List[Any] = List(1, 2, 3)

Any entspricht dem Object von Java. Es ist der Obertyp aller Objekte in Scala, in ihn kann also immer gecastet werden. Grundsätzlich sollte man Casts aber immer vermeiden, da sie fehleranfällig sind. Es gibt in Scala andere, bessere Wege um auf korrekte Typen zu überprüfen. Von diesen werdet ihr noch früh genug etwas hören. Zum Schluss noch eine Graphik, die Scalas wichtigste Typen auflistet:

Advertisements

No comments yet

Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden / Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden / Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden / Ändern )

Google+ Foto

Du kommentierst mit Deinem Google+-Konto. Abmelden / Ändern )

Verbinde mit %s

%d Bloggern gefällt das: