57 Stimmen

Warum sind Arrays invariant, Listen aber kovariant?

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?

80voto

Op De Cirkel Punkte 27523

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

25voto

sshannin Punkte 2697

Das liegt daran, dass Listen unveränderlich und Arrays veränderbar sind.

9voto

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] ).

7voto

Yuhuan Jiang Punkte 2496

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.com

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.

Powered by:

X