Scala中以下泛型定义之间的区别是什么:
class Foo[T <: List[_]]
和
class Bar[T <: List[Any]]
我的直觉告诉我它们大致相同,但后者更明确。我发现前者可以编译但后者不能编译的情况,但不能使我完全了解。
谢谢!
编辑:
我可以混入另一个吗?
class Baz[T <: List[_ <: Any]]
最佳答案
好的,我认为我应该对此负责,而不仅仅是发表评论。抱歉,如果您想让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的答案所引用的文章的作者自己弄错了(请参阅评论),并在以后进行了修复。我与本文有关的主要问题是,在所示的示例中,使用存在性应该使我们免于键入问题,但事实并非如此。去检查代码,然后尝试编译
compileAndRun(helloWorldVM("Test"))
或compileAndRun(intVM(42))
。是的,不会编译。只需使compileAndRun
在A
中通用,就可以使代码编译,并且将更加简单。简而言之,这可能不是学习存在物及其优点的最佳文章(作者本人在评论中承认该文章“需要整理”)。
因此,我宁愿建议您阅读本文:http://www.artima.com/scalazine/articles/scalas_type_system.html,特别是名为“Existential type”和“Java and Scala中的方差”的部分。
从本文中可以得出的重要一点是,在处理非协变量类型时,存在性是有用的(除了能够处理通用的Java类)。
这是一个例子。
case class Greets[T]( private val name: T ) {
def hello() { println("Hello " + name) }
def getName: T = name
}
这个类是通用的(也请注意它是不变的),但是我们可以看到
hello
确实不使用type参数(与getName
不同),因此,如果我获得Greets
的实例,我应该总是可以调用它,无论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))
太好了,这可行。我们也可以在这里使用存在性:
def sayHi3( g: Greets[_] ) { g.hello() }
sayHi3( Greets("John"))
sayHi3( Greets('Jack))
也可以。因此,总而言之,在类型参数(如
sayHi3
)中使用存在性(如sayHi2
)并没有真正的好处。但是,如果
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]
),那么一切都将立即可用,我们将永远不需要存在。综上所述,存在性对于处理泛型不变量类很有用,但是如果泛型类不需要将自身作为类型参数出现在另一个泛型类中,则很有可能您不需要存在性,而只需添加类型参数你的方法会起作用
现在回到(最后,我知道!)您的特定问题,关于
class Foo[T <: List[_]]
因为
List
是协变的,所以对于所有意图和目的,这就像在说:class Foo[T <: List[Any]]
因此,在这种情况下,使用任何一种表示方式实际上只是样式问题。
但是,如果将
List
替换为Set
,情况将发生变化:class Foo[T <: Set[_]]
Set
是不变的,因此我们处于与示例中的Greets
类相同的情况。因此,上述内容确实与class Foo[T <: Set[Any]]