阅读时间: 5 分钟
通用类是将一个类型作为参数的类。这意味着,一个类可以用于不同的类型,而实际上不需要多次写下它。这些对于集合类特别有用。
1.概述
在这篇博客中,我们将探讨Scala泛型在实现容器方面的优势。我们将研究Scala泛型如何提供类型安全,同时帮助我们坚持DRY原则。
我们将经历编写泛型类和方法的步骤,并探索标准Scala库中可用的泛型类型。
2.泛型方法。
在编写接受泛型输入或产生泛型返回值的Scala方法时,这些方法本身可能是泛型的,也可能不是。
2.1.泛型声明语法。
声明泛型方法与声明泛型类非常相似。 我们仍然将类型参数放在方括号中。
而且,方法的返回类型也可以被参数化。
def middle[A](input: Seq[A]): A = input(input.size / 2)
这个方法接收一个包含选定类型的项目的Seq,并返回Seq中间的项目。
val rabbits = List[Rabbit](Rabbit(2), Rabbit(3), Rabbit(7))
val middleRabbit: Rabbit = middle[Rabbit](rabbits)
现在让我们来看看一个有多个类型参数的例子。
def itemsAt[A, B](index: Int, seq1: Seq[A], seq2: Seq[B]): (A, B) = (seq1(index), seq2(index))
这个方法接收两个Seq中的索引元素,并返回一个元组,与输入中使用的类型匹配。
val apples = List[Apple](Apple("gala"), Apple("pink lady"))
val items: (Rabbit, Apple) = itemsAt[Rabbit, Apple](1, rabbits, apples)
我们应该注意到,上面的例子并不适合生产,因为它们不能正确处理诸如空Seq这样的边缘情况。然而,它们展示了类型参数如何帮助我们强制执行参数和返回值的类型。
2.2.在非泛型方法中使用泛型类。
在使用泛型类时,我们并不总是写泛型方法。例如,一个接收两个任何类型的Lists并返回总长度的方法可以被声明为。
def totalSize(list1: List[_], list2: List[_]): Int
_(下划线)意味着List中的内容并不重要。这里没有类型参数,因此,调用者不需要为这个方法提供类型。
val rabbits = List[Rabbit](Rabbit(2), Rabbit(3), Rabbit(7))
val strings = List("a", "b")
val size: Int = totalSize(rabbits, strings)
在这个例子中,当函数被调用时,类型没有被提供给它。
3.上层类型界线
为了说明Scala的上层类型界限,让我们重新发明轮子,编写一个基本的但又是泛型的函数来寻找一个集合中的最大元素。下面是我们的第一次尝试。
def findMax[T](xs: List[T]): Option[T] = xs.reduceOption((x1, x2) => if (x1 >= x2) x1 else x2)
尽管逻辑上似乎没有问题,但是*>=函数并没有定义在泛型T*上。
我们知道,Ordered[T]是这种比较函数的归宿。所以,**我们应该以某种方式告诉编译器,在这个例子中,T是*Ordered[T]***的一个子类型。
事实证明,Scala泛型中的上界类型可以为我们做到这一点。
def findMax[T <: Ordered[T]](xs: List[T]): Option[T] = xs.reduceOption((x1, x2) => if (x1 >= x2) x1 else x2)
通过"T <:语法,我们表示Ordered[T]是类型参数T**的上位类型,也就是说,xs列表中的每个元素都应该是*Ordered[T]的子类型。这样,我们就可以对其使用>=*和其他比较函数。
4.类型下限
让我们举一个例子。
class Queue[+T](private val leading: List[T], trailing: List[T]) {
def head(): T = // returns the first element
def tail(): List[T] = // everything but the first element
def enqueue(x: T): Queue[T] = // appending to the end
}
这里,我们试图用两个列表来表示一个队列。
现在,假设我们需要处理一个Queue[String]。因为我们希望Queue[String]是Queue[Any] 的子类型,所以我们使用了共变类型注解*[+T]。*然而,现在 enqueue 方法无法编译。
covariant type T occurs in contravariant position in type T of value x
def enqueue(x: T): Queue[T] = new Queue(leading, x :: trailing)
类型参数*[+T]*是协变的,但我们把它用在了协变的位置(函数参数),编译器对此提出了抱怨。
解决这个问题的一个方法是使用下限类型。
def enqueue[U >: T](x: U): Queue[U] = new Queue(leading, x :: trailing)
在这里,我们要定义一个新的类型参数U,同时,"U >:T "的语法意味着U应该是U的一个超类型。T现在,我们可以使用enqueue方法了。
val empty = new Queue[String](Nil, Nil)
val stringQ: Queue[String] = empty.enqueue("The answer")
val intQ: Queue[Any] = stringQ.enqueue(42)
当我们向Queue[String]中添加Int时,编译器会自动推断出离String和Int最近的超类型。 因此返回的类型是Queue[Any]。
Variance定义了参数化类型的继承关系。它是所有关于子类型的。它使Scala集合更加类型安全。
Variance的类型:
class Stack[+A] // A covariant class
class Stack[-A] // A contravariant class
class Stack[A] // An invariant class
5.1 共变体
如果S是T的一个子类型,那么List[S]就是List[T]的一个子类型。

在Scala中,表示两个参数化类型之间协变关系的语法是在Type Parameter前加上 "+"符号。让我们借助于一个例子来理解协变关系。
abstract class Flower
{
def name: String
}
// Creating a sub-class Lily
// of Flower
case class Lily(name: String) extends Flower
// Creating a sub-class Carnation
// of Flower
case class Carnation(name: String) extends Flower
object Covariance extends App
{
// Creating a method
def FlowerNames(flowers: List[Flower]): Unit =
{
flowers.foreach
{
flower => println(flower.name)
}
}
// Assigning names
val lily: List[Lily] = List(Lily("White Lily"),
Lily("Jersey Lily"))
val carnations: List[Carnation] = List(Carnation("White carnations"),
Carnation("Pink carnations"))
// Print: names of lily
FlowerNames(lily)
// Print: names of carnation
FlowerNames(carnations)
}
5.2 等价关系
如果S是T的一个子类型,那么List[T]就是List[S]的一个子类型。

在Scala中,表示两个参数化类型之间的共变关系的语法是在Type Parameter前加上"-"符号。让我们借助于一个例子来理解反变量。
abstract class Type[-T]{
def typeName(): Unit
}
class SuperType extends Type[AnyVal]{
override def typeName(): Unit = {
print("\n\n SuperType \n\n")
}
}
class SubType extends Type[Int]{
override def typeName(): Unit = {
print("\n\n SubType \n\n")
}
}
class TypeCarer{
def display(t: Type[Int]){
t.typeName()
}
}
object ScalaContravariance {
def main(args: Array[String]) {
val superType = new SuperType
val subType = new SubType
val typeCarer = new TypeCarer
typeCarer.display(subType)
typeCarer.display(superType)
}
}
5.3 不变量
如果S是T的一个子类型,那么List[S]和List[T]就没有继承关系或子类型。这意味着两者是不相关的。
在Scala中,默认情况下,泛型类型具有非变异关系。如果我们不使用 "+"或**"-"**符号来定义Parameterized Types,那么它们就被称为Invariants。让我们借助于一个例子来理解反变量。
abstract class Animal {
def name: String
}
case class Cat(name: String) extends Animal
case class Dog(name: String) extends Animal
class Container[A](value: A) {
private var _value: A = value
def getValue: A = _value
def setValue(value: A): Unit = {
_value = value
}
}
object Invariance {
val catContainer: Container[Cat] = new Container[Cat](Cat("tom"))
// This won't compile
val animalContainer: Container[Animal] = catContainer
}
6.总结
总结这篇博客,我们得出以下结论。
- 通用类是以类型为参数的类,也就是说,一个类可以用于不同的类型,而不需要实际写下多次。
- 泛型类的方法
- 差异性定义了参数化类型的继承关系。
- 各种类型的变异是。不变型、共变型和同变型。
- 不变型。如果S是T的一个子类型,那么**List[S]和List[T]**不存在继承关系或子类型。
- 不变量。如果S是T的一个子类型,那么List[S]就是List[T]的一个子类型。
- 不变的。如果S是T的一个子类型,那么List[T]是List[S]的一个子类型。