好的,我想我应该发表自己的看法,而不仅仅是发表评论。如果你只想看简短版,请跳到结尾。抱歉,这篇文章会比较长。
正如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答案引用的文章作者自己也犯了错误(见评论),后来进行了修正。我对这篇文章的主要问题在于,在示例中,存在性的使用应该可以让我们免受打字错误的困扰,但实际上并没有起到这样的作用。去检查一下代码,尝试编译
compileAndRun(helloWorldVM("Test"))
或
compileAndRun(intVM(42))
。是的,无法编译。只需将
compileAndRun
泛型化为
A
即可使代码编译,并且会更简单。
简而言之,这可能不是学习存在性及其用途的最佳文章(作者本人在评论中承认文章“需要整理”)。
因此,我更愿意推荐阅读这篇文章:
http://www.artima.com/scalazine/articles/scalas_type_system.html,特别是名为“存在类型”和“Java和Scala中的变化性”的部分。
你应该从这篇文章中得到的重要观点是,存在类型在处理非协变类型时非常有用(除了能够处理通用的java类)。以下是一个例子。
case class Greets[T]( private val name: T ) {
def hello() { println("Hello " + name) }
def getName: T = name
}
这个类是泛型的(注意它是不变的),但我们可以看到hello
实际上并没有使用类型参数(不像getName
),所以如果我得到一个Greets
的实例,无论T
是什么,我都应该能够调用它。如果我想定义一个方法,接受一个Greets
实例并只调用它的hello
方法,我可以尝试这样做:
def sayHi1( g: Greets[T] ) { g.hello() }
果然,这段代码无法编译,因为T
在此处不可见。
好的,那么让我们将该方法改为泛型方法:
def sayHi2[T]( g: Greets[T] ) { g.hello() }
sayHi2( Greets("John"))
sayHi2( Greets('Jack))
太好了,这可行。我们还可以在这里使用存在论:
def sayHi3( g: Greets[_] ) { g.hello() }
sayHi3( Greets("John"))
sayHi3( Greets('Jack))
同样有效。总的来说,在使用存在类型(如sayHi3
)和类型参数(如sayHi2
)方面,没有真正的好处。
然而,如果Greets
本身作为另一个泛型类的类型参数出现,则情况会有所改变。例如,我们想在列表中存储多个不同T
的Greets
实例。让我们试一试:
val greets1: Greets[String] = Greets("John")
val greets2: Greets[Symbol] = Greets('Jack)
val greetsList1: List[Greets[Any]] = List( greets1, greets2 )
最后一行无法编译,因为
Greets
是不变的,所以
Greets[String]
和
Greets[Symbol]
不能被视为
Greets[Any]
,即使
String
和
Symbol
都扩展了
Any
。
好的,让我们尝试使用存在类型,使用简写符号_
:
val greetsList2: List[Greets[_]] = List( greets1, greets2 )
这段代码可以成功编译,并且你可以按照预期进行操作:
greetsSet foreach (_.hello)
现在,请记住我们在第一次遇到类型检查问题的原因是因为Greets是不变的。如果将其转换为协变类(class Greets[+T]),那么一切都会顺利进行,我们永远不需要存在性。
总之,存在性对于处理通用不变类非常有用,但是如果通用类不需要本身作为另一个通用类的类型参数出现,则很可能不需要存在性,只需将类型参数添加到方法中即可。
现在回到你的具体问题。关于
class Foo[T <: List[_]]
由于List
是协变的,因此这与仅仅说:
实际上是相同的。
class Foo[T <: List[Any]]
因此,在这种情况下,使用任一符号只是一个风格问题。
然而,如果你将List
替换为Set
,情况就会改变:
class Foo[T <: Set[_]]
Set
是不变的,因此我们面临着与我例子中的 Greets
类相同的情况。因此,以上内容与之前的内容确实非常不同。
class Foo[T <: Set[Any]]
<: Any
不会改变任何东西。在 Scala 中,每个类型都是<: Any
。 - Randall Schulz