好的,我想我应该接受它,而不是仅仅发布 cmets。抱歉,这会很长,如果您希望 TL;DR 跳到最后。
正如 Randall Schulz 所说,这里的 _ 是存在类型的简写。即,
class Foo[T <: List[_]]
是
的简写
class Foo[T <: List[Z] forSome { type Z }]
请注意,与 Randall Shulz 的回答所提到的相反(完全披露:我在这篇文章的早期版本中也弄错了,感谢 Jesper Nordenberg 指出)这与以下内容不同:
class Foo[T <: List[Z]] forSome { type Z }
也不等同于:
class Foo[T <: List[Z forSome { type Z }]]
当心,很容易出错(正如我之前的傻瓜所示):Randall Shulz 的回答所引用的文章的作者自己弄错了(参见 cmets),后来修复了它。我对这篇文章的主要问题是,在显示的示例中,存在主义的使用本应使我们免于打字问题,但事实并非如此。去检查代码,并尝试编译compileAndRun(helloWorldVM("Test")) 或compileAndRun(intVM(42))。是的,不编译。在A 中简单地使compileAndRun 泛型将使代码编译,并且它会简单得多。
简而言之,这可能不是了解存在主义及其好处的最佳文章(作者本人在评论中承认该文章“需要整理”)。
所以我宁愿推荐阅读这篇文章:http://www.artima.com/scalazine/articles/scalas_type_system.html,尤其是名为“Existential types”和“Variance in Java and Scala”的部分。
您应该从本文中得到的重要一点是,在处理非协变类型时,存在函数很有用(除了能够处理泛型 Java 类之外)。
这是一个例子。
case class Greets[T]( private val name: T ) {
def hello() { println("Hello " + name) }
def getName: T = name
}
这个类是通用的(注意它是不变的),但我们可以看到hello 真的没有使用类型参数(不像getName),所以如果我得到一个@ 的实例987654336@我应该总是可以打电话给它,不管T是什么。如果我想定义一个采用Greets 实例并只调用其hello 方法的方法,我可以试试这个:
def sayHi1( g: Greets[T] ) { g.hello() } // Does not compile
果然,这不会编译,因为 T 不知从何而来。
好吧,让我们将方法设为泛型:
def sayHi2[T]( g: Greets[T] ) { g.hello() }
sayHi2( Greets("John"))
sayHi2( Greets('Jack))
太好了,这行得通。我们也可以在这里使用existentials:
def sayHi3( g: Greets[_] ) { g.hello() }
sayHi3( Greets("John"))
sayHi3( Greets('Jack))
也可以。所以总而言之,在类型参数(如sayHi2)上使用存在(如sayHi3)并没有真正的好处。
但是,如果Greets 将自身作为另一个泛型类的类型参数出现,则情况会发生变化。举例来说,我们想在一个列表中存储多个Greets(具有不同的T)实例。让我们试试吧:
val greets1: Greets[String] = Greets("John")
val greets2: Greets[Symbol] = Greets('Jack)
val greetsList1: List[Greets[Any]] = List( greets1, greets2 ) // Does not compile
最后一行没有编译,因为Greets 是不变的,所以Greets[String] 和Greets[Symbol] 不能被视为Greets[Any],即使String 和Symbol 都扩展了Any。
好的,让我们尝试使用简写符号_:
val greetsList2: List[Greets[_]] = List( greets1, greets2 ) // Compiles fine, yeah
这编译得很好,你可以按预期做:
greetsSet foreach (_.hello)
现在,请记住,我们首先遇到类型检查问题的原因是因为Greets 是不变的。如果将其转换为协变类 (class Greets[+T]),那么一切都会开箱即用,我们将永远不需要存在主义。
总而言之,existentials 对于处理泛型不变类很有用,但是如果泛型类不需要将自身作为另一个泛型类的类型参数出现,那么您可能不需要存在主义并简单地添加您的方法的类型参数将起作用
现在回到(终于,我知道了!)您的具体问题,关于
class Foo[T <: List[_]]
因为List 是协变的,所以这与刚才说的所有意图和目的相同:
class Foo[T <: List[Any]]
所以在这种情况下,使用任何一种表示法实际上只是风格问题。
但是,如果将 List 替换为 Set,情况就会发生变化:
class Foo[T <: Set[_]]
Set 是不变的,因此我们的情况与我的示例中的 Greets 类相同。因此,上面的内容确实与
class Foo[T <: Set[Any]]