Zum Beispiel: Warum ist
val list:List[Any] = List[Int](1,2,3)
arbeiten, aber
val arr:Array[Any] = Array[Int](1,2,3)
scheitert (weil Arrays unveränderlich sind). Was ist der gewünschte Effekt hinter dieser Designentscheidung?
Zum Beispiel: Warum ist
val list:List[Any] = List[Int](1,2,3)
arbeiten, aber
val arr:Array[Any] = Array[Int](1,2,3)
scheitert (weil Arrays unveränderlich sind). Was ist der gewünschte Effekt hinter dieser Designentscheidung?
Denn sonst würde die Typsicherheit verletzt. Wenn nicht, könnten Sie so etwas wie dies tun:
val arr:Array[Int] = Array[Int](1,2,3)
val arr2:Array[Any] = arr
arr2(0) = 2.54
und der Compiler kann es nicht abfangen.
Andererseits sind Listen unveränderlich, so dass man nichts hinzufügen kann, was nicht schon vorhanden ist. Int
Die übliche Antwort ist, dass die Mutabilität in Kombination mit der Kovarianz die Typsicherheit aufheben würde. Für Sammlungen kann dies als eine grundlegende Wahrheit angesehen werden. Aber die Theorie gilt eigentlich für jeden generischen Typ, nicht nur für Sammlungen wie List
y Array
und wir müssen gar nicht erst versuchen, über die Veränderlichkeit nachzudenken.
Die eigentliche Antwort hat mit der Art und Weise zu tun, wie Funktionstypen mit der Subtypisierung interagieren. Kurz gesagt, wenn ein Typparameter als Rückgabetyp verwendet wird, ist er kovariant. Wird ein Typparameter dagegen als Argumenttyp verwendet, ist er kontravariant. Wenn er sowohl als Rückgabetyp als auch als Argumenttyp verwendet wird, ist er invariant.
Schauen wir uns die Dokumentation für Array[T]
. Die beiden offensichtlichen Methoden, die man sich ansehen sollte, sind die für Lookup und Update:
def apply(i: Int): T
def update(i: Int, x: T): Unit
Bei der ersten Methode T
ist ein Rückgabetyp, während in der zweiten T
ist ein Argumenttyp. Die Varianzregeln schreiben vor, dass T
muss daher unveränderlich sein.
Wir können die Dokumentation für List[A]
um zu sehen, warum sie kovariant ist. Verwirrenderweise würden wir diese Methoden, die analog zu den Methoden für Array[T]
:
def apply(n: Int): A
def ::(x: A): List[A]
Desde A
sowohl als Rückgabetyp als auch als Argumenttyp verwendet wird, würde man erwarten A
unveränderlich zu sein, genau wie T
ist für Array[T]
. Doch anders als bei Array[T]
belügt uns die Dokumentation über die Art der ::
. Die Lüge ist gut genug für die meisten Aufrufe dieser Methode, aber sie ist nicht gut genug, um die Varianz von A
. Wenn wir die Dokumentation für diese Methode erweitern und auf "Full Signature" klicken, wird uns die Wahrheit gezeigt:
def ::[B >: A](x: B): List[B]
Donc A
erscheint nicht als Argumenttyp. Stattdessen, B
(das kann ein beliebiger Supertyp von A
) ist der Argumenttyp. Dies stellt keine Einschränkung für A
so dass sie wirklich kovariant sein kann. Jede Methode auf List[A]
die über A
als Argumenttyp ist eine ähnliche Lüge (wir erkennen das daran, dass diese Methoden als [use case]
).
Der Unterschied besteht darin, dass List
s unveränderlich sind, während Array
s sind veränderbar.
Um zu verstehen, warum die Veränderlichkeit die Varianz bestimmt, sollten Sie eine veränderbare Version von List
- nennen wir es MutableList
. Wir werden auch einige Beispieltypen verwenden: eine Basisklasse Animal
und 2 Unterklassen namens Cat
y Dog
.
trait Animal {
def makeSound: String
}
class Cat extends Animal {
def makeSound = "meow"
def jump = // ...
}
class Dog extends Animal {
def makeSound = "bark"
}
Beachten Sie, dass Cat
hat eine weitere Methode ( jump
) als Dog
.
Definieren Sie dann eine Funktion, die eine veränderbare Liste von Tieren annimmt und die Liste verändert:
def mindlessFunc(xs: MutableList[Animal]) = {
xs += new Dog()
}
Wenn Sie nun eine Liste von Katzen an die Funktion übergeben, passieren schreckliche Dinge:
val cats = MutableList[Cat](cat1, cat2)
val horror = mindlessFunc(cats)
Wenn wir eine nachlässige Programmiersprache verwenden, wird dies bei der Kompilierung ignoriert. Dennoch wird unsere Welt nicht zusammenbrechen, wenn wir nur mit dem folgenden Code auf die Liste der Katzen zugreifen:
cats.foreach(c => c.makeSound)
Aber wenn wir das tun:
cats.foreach(c => c.jump)
Es wird ein Laufzeitfehler auftreten. Mit Scala wird das Schreiben von solchem Code verhindert, da sich der Compiler beschwert.
CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.