scala知识体系04_泛型
本文主要讲解泛型、类型边界、协变、逆变的基础概念和应用。
泛型定义和调用
泛型是值定义以类型为参数的类,在scala
源码中对泛型的使用相当广泛。
一般使用字母A
作为类型参数标识符,并放在方括号[]
中。如果有多个类型参数,则可以依次用A,B,C等参数名称,如scala.collection.immutable
包中特征Map
中定义如下:
trait Map[A, +B] extends Iterable[(A, B)] with scala.collection.Map[A, B] with MapLike[A, B, Map[A, B]] { self => override def empty: Map[A, B] = Map.empty override def toMap[T, U](implicit ev: (A, B) <:< (T, U)): immutable.Map[T, U] = self.asInstanceOf[immutable.Map[T, U]] override def seq: Map[A, B] = this def withDefault[B1 >: B](d: A => B1): immutable.Map[A, B1] = new Map.WithDefault[A, B1](this, d) def withDefaultValue[B1 >: B](d: B1): immutable.Map[A, B1] = new Map.WithDefault[A, B1](this, x => d) override def updated [B1 >: B](key: A, value: B1): Map[A, B1] def + [B1 >: B](kv: (A, B1)): Map[A, B1]}
如下我们自定义泛型类MyStack:
class MyStack[A] { private var elements: List[A] = Nil def push(x: A) { elements = x :: elements } def peek: A = elements.head def pop(): A = { val currentTop = peek elements = elements.tail currentTop } }
MyStack
类的实现将任何类型A作为参数。这意味着
底层列表var elements: List[A] = Nil
只能存储类型的元素A。
该程序方法push
只接受类型的对象A。
泛型类的调用和一般类的调用一致,只不过使用前先指定具体的数据类型即可,如下:
object GenericClassDemo { class MyStack[A] { private var elements: List[A] = Nil def push(x: A) { elements = x :: elements } def peek: A = elements.head def pop(): A = { val currentTop = peek elements = elements.tail currentTop } def length(): Int = { elements.size } } abstract class Fruit { def name:String } case class Apple(name: String) extends Fruit case class Banana(name: String) extends Fruit def main(args: Array[String]) { val stack = new MyStack[Fruit] val apple = new Apple("Gala") val banana = new Banana("Cactus") stack.push(apple) stack.push(banana) println(stack.length) }}
我们向类型参数为Fruit
实例stack
,并push
一个apple
和banana
,最后打印了堆栈的长度。
可以看到由于apple
和banana
均是Fruit
的子类型,能push
到stack
中,但如果push
其他类型的实例肯定会编译不通过。此外,为控制泛型类型的子类型的行为,scala
采用了类型参数变型机制,主要包括协变、逆变和非变。
参数类型的边界
scala中,类型参数和抽象类型受到类型绑定的约束,类型边界约束了类型变量的具体值。
讨论变型前,先看看类型的上界和下界的定义。
上界B <: A
声明类型变量B是类型A的子类型。
下界B >: A
声明参数类型或者抽象类型B是类型A的超类型。大多数场合下,A是类的类型参数,B是函数的类型参数。
协变
通过注释+A
可以使泛型类的参数A变为协变类型。
如果类F的类型参数是协变类型,对于两种类型A和B,如果B是A的子类型(B<:A),则F[B]也是F[A]的子类型(F[B]<:F[A])。
如scala源码中scala.collection.immutable.List类的类型参数是协变类型, 定义如下:
sealed abstract class List[+A] extends AbstractSeq[A] with LinearSeq[A] with Product with GenericTraversableTemplate[A, List] with LinearSeqOptimized[A, List[A]] with Serializable { def tail: List[A] override def drop(n: Int): List[A] = { var these = this var count = n while (!these.isEmpty && count > 0) { these = these.tail count -= 1 } these } // something else}
在如下的代码示例中,printFruitName
方法输入参数为List[Fruit]
,由于List
是协变的,因而输入List[Fruit]
的子类型List[Apple]
和List[Banana]
也是允许的。
object CovarianceDemo { abstract class Fruit { def name:String } case class Apple(name: String) extends Fruit case class Banana(name: String) extends Fruit def printFruitName(fruits: List[Fruit]): Unit = { fruits.foreach(fruit => println(fruit.name)) } def main(args: Array[String]) { val apples: List[Apple] = List(new Apple("Gala Apple"), new Apple("Flowercow Apple")) val bananas: List[Banana] = List(new Banana("Banana A"), new Banana("Banana B")) printFruitName(apples) printFruitName(bananas) }}
运行结果如下:
Gala AppleFlowercow AppleBanana ABanana B
逆变
通过注释-A
可以使泛型类的参数A变为逆变类型。
如果类F的类型参数是逆变类型,对于两种类型A和B,如果B是A的子类型(B<:A
),则F[A]
是F[B]
的子类型(F[A]<:F[B]
)。
如下的代码示例中,看我们定义Printer
类,其参数为逆变类型,由于Apple
是Fruit
的子类型,我们可以知道Printer[Fruit]
是Printer[Apple]
的子类型,这可以从函数printFruitName
得到印证。我们要求的输入参数是Printer[Apple]
类型,而实际输入Printer[Fruit]
和Printer[Apple]
都能正常输出结果。
object ContravarianceDemo { abstract class Fruit { def name: String } case class Apple(name: String) extends Fruit case class Banana(name: String) extends Fruit abstract class Printer[-A] { def print(a: A): Unit } class FruitPrinter extends Printer[Fruit] { override def print(fruit: Fruit): Unit = { println(s"fruit name is ${fruit.name}") } } class ApplePrinter extends Printer[Apple] { override def print(apple: Apple): Unit = { println(s"apple name is ${apple.name}") } } def main(args: Array[String]) { val apple: Apple = new Apple("Gala Apple") val fruitPrinter: Printer[Fruit] = new FruitPrinter val applePrinter: Printer[Apple] = new ApplePrinter def printFruitName(printer: Printer[Apple]): Unit = { printer.print(apple) } printFruitName(fruitPrinter) printFruitName(applePrinter) }}
输出如下:
fruit name is Gala Appleapple name is Gala Apple
非变
默认情况下,出于安全考虑,泛型类的参数类型是非变的,即不是协变的,也不是逆变的。
这种情况在scala源码中应用比较广泛,比如Array类,不再赘述。
本文参考内容如下:
https://docs.scala-lang.org/
https://www.scala-lang.org/api/current/
http://twitter.github.io/scala_school/zh_cn/index.html
http://twitter.github.io/effectivescala/index-cn.html
查看评论 回复