OK, ich dachte mir, ich sollte meine Meinung dazu haben, anstatt nur Kommentare zu posten. Entschuldigung, dies wird lange dauern, wenn Sie die TL; DR bis zum Ende überspringen möchten.
Wie Randall Schulz sagte, ist hier _
eine Abkürzung für einen existenziellen Typ. Nämlich,
class Foo[T <: List[_]]
ist eine Abkürzung für
class Foo[T <: List[Z] forSome { type Z }]
Beachten Sie, dass im Gegensatz zu Randall Shulz 'Antwort (vollständige Offenlegung: Ich habe es auch in einer früheren Version dieses Beitrags falsch verstanden, danke an Jesper Nordenberg für den Hinweis) dies nicht dasselbe ist wie:
class Foo[T <: List[Z]] forSome { type Z }
noch ist es das gleiche wie:
class Foo[T <: List[Z forSome { type Z }]]
Achtung, es ist leicht, etwas falsch zu machen (wie mein früherer Fehler zeigt): Der Autor des Artikels, auf den Randall Shulz 'Antwort verweist, hat es selbst falsch verstanden (siehe Kommentare) und später behoben. Mein Hauptproblem bei diesem Artikel ist, dass in dem gezeigten Beispiel die Verwendung von Existentials uns vor einem Tippproblem bewahren soll, dies jedoch nicht. Überprüfen Sie den Code und versuchen Sie, compileAndRun(helloWorldVM("Test"))
oder zu kompilieren compileAndRun(intVM(42))
. Ja, kompiliert nicht. Einfach compileAndRun
generisch machenA
würde den Code Kompilierung machen, und es wäre viel einfacher sein. Kurz gesagt, das ist wahrscheinlich nicht der beste Artikel, um etwas über Existentiale und deren Zweck zu erfahren (der Autor selbst bestätigt in einem Kommentar, dass der Artikel "aufgeräumt werden muss").
Daher würde ich eher empfehlen, diesen Artikel zu lesen: http://www.artima.com/scalazine/articles/scalas_type_system.html , insbesondere die Abschnitte "Existenzielle Typen" und "Varianz in Java und Scala".
Der wichtige Punkt, den Sie aus diesem Artikel erhalten sollten, ist, dass Existenziale nützlich sind (abgesehen davon, dass sie mit generischen Java-Klassen umgehen können), wenn es um nicht kovariante Typen geht. Hier ist ein Beispiel.
case class Greets[T]( private val name: T ) {
def hello() { println("Hello " + name) }
def getName: T = name
}
Diese Klasse ist generisch (beachten Sie auch, dass sie unveränderlich ist), aber wir können sehen, dass hello
der Typparameter (im Gegensatz zu getName
) wirklich nicht verwendet wird. Wenn ich also eine Instanz von bekomme, Greets
sollte ich sie immer aufrufen können, wie auch immer T
ist. Wenn ich eine Methode definieren möchte, die eine Greets
Instanz nimmt und nur ihre hello
Methode aufruft , könnte ich Folgendes versuchen:
def sayHi1( g: Greets[T] ) { g.hello() }
Sicher genug, dies kompiliert nicht, wie T
hier aus dem Nichts kommt.
OK, dann machen wir die Methode generisch:
def sayHi2[T]( g: Greets[T] ) { g.hello() }
sayHi2( Greets("John"))
sayHi2( Greets('Jack))
Großartig, das funktioniert. Wir könnten hier auch Existentials verwenden:
def sayHi3( g: Greets[_] ) { g.hello() }
sayHi3( Greets("John"))
sayHi3( Greets('Jack))
Funktioniert auch. Alles in allem gibt es hier also keinen wirklichen Vorteil, wenn ein existenzieller (wie in sayHi3
) gegenüber einem Typparameter (wie in sayHi2
) verwendet wird.
Dies ändert Greets
sich jedoch, wenn es selbst als Typparameter für eine andere generische Klasse angezeigt wird. Angenommen, wir möchten mehrere Instanzen von Greets
(mit unterschiedlichen T
) in einer Liste speichern . Lass es uns versuchen:
val greets1: Greets[String] = Greets("John")
val greets2: Greets[Symbol] = Greets('Jack)
val greetsList1: List[Greets[Any]] = List( greets1, greets2 )
Die letzte Zeile wird nicht kompiliert, da sie Greets
unveränderlich ist, sodass a Greets[String]
und Greets[Symbol]
nicht als Greets[Any]
obwohl behandelt werden kann String
und Symbol
beide erweitert werden Any
.
OK, versuchen wir es mit einem Existenziellen unter Verwendung der Kurzschreibweise _
:
val greetsList2: List[Greets[_]] = List( greets1, greets2 )
Dies lässt sich gut kompilieren, und Sie können wie erwartet Folgendes tun:
greetsSet foreach (_.hello)
Denken Sie jetzt daran, dass der Grund, warum wir überhaupt ein Problem mit der Typprüfung hatten, darin bestand, dass Greets
es unveränderlich ist. Wenn es in eine kovariante Klasse ( class Greets[+T]
) umgewandelt worden wäre, hätte alles sofort geklappt und wir hätten niemals Existenziale benötigt.
Zusammenfassend lässt sich sagen, dass Existentials nützlich sind, um mit generischen invarianten Klassen umzugehen. Wenn die generische Klasse jedoch nicht als Typparameter für eine andere generische Klasse angezeigt werden muss, benötigen Sie wahrscheinlich keine Existentials und fügen einfach einen Typparameter hinzu zu Ihrer Methode wird funktionieren
Kommen Sie nun (endlich, ich weiß!) Auf Ihre spezifische Frage zurück
class Foo[T <: List[_]]
Weil List
es kovariant ist, ist dies in jeder Hinsicht dasselbe wie nur zu sagen:
class Foo[T <: List[Any]]
In diesem Fall ist die Verwendung einer der beiden Notationen also nur eine Frage des Stils.
Wenn Sie jedoch ersetzen List
mit Set
, Dinge zu ändern:
class Foo[T <: Set[_]]
Set
ist invariant und somit befinden wir uns in der gleichen Situation wie bei der Greets
Klasse aus meinem Beispiel. Somit unterscheidet sich das Obige wirklich sehr von
class Foo[T <: Set[Any]]
<: Any
niemals etwas ändert. Jeder Typ in Scala ist<: Any
.