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 compileAndRungenerisch 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 helloder Typparameter (im Gegensatz zu getName) wirklich nicht verwendet wird. Wenn ich also eine Instanz von bekomme, Greetssollte ich sie immer aufrufen können, wie auch immer Tist. Wenn ich eine Methode definieren möchte, die eine GreetsInstanz nimmt und nur ihre helloMethode aufruft , könnte ich Folgendes versuchen:
def sayHi1( g: Greets[T] ) { g.hello() }
Sicher genug, dies kompiliert nicht, wie Thier 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 Greetssich 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 Greetsunveränderlich ist, sodass a Greets[String]und Greets[Symbol]nicht als Greets[Any]obwohl behandelt werden kann Stringund Symbolbeide 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 Greetses 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 Listes 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 Listmit Set, Dinge zu ändern:
class Foo[T <: Set[_]]
Setist invariant und somit befinden wir uns in der gleichen Situation wie bei der GreetsKlasse aus meinem Beispiel. Somit unterscheidet sich das Obige wirklich sehr von
class Foo[T <: Set[Any]]
<: Anyniemals etwas ändert. Jeder Typ in Scala ist<: Any.