1.1泛型类
- 泛型类是将类型作为参数的类。它们对集合类特别有用。
- 定义泛类型:泛型类将类型作为方括号内的参数
[]
。一种惯例是使用字母A
作为类型参数标识符,但是可以使用任何参数名称。
class Stack[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 } }
tack
该类的实现将任何类型A
作为参数。这意味着底层列表var elements: List[A] = Nil
只能存储类型的元素A
。该过程def push
只接受类型的对象A
(注意:elements = x :: elements
重新分配elements
到通过前置x
当前创建的新列表elements
)
-
用法:要使用泛型类,请将类型放在方括号中代替
A。
val stack = new Stack[Int] stack.push(1) stack.push(2) println(stack.pop) println(stack.pop)
结果如下:
该实例stack
只能使用Int。但是,如果type参数有子类型,那么可以传入,类Apple
和Banana
两个延伸Fruit
,所以我们可以推实例apple
和banana
到的堆叠Fruit
。
class Fruit class Apple extends Fruit class Banana extends Fruit val stack = new Stack[Fruit] val apple = new Apple val banana = new Banana stack.push(apple) stack.push(banana)
注:泛型类型的子类型是*,且*不变。这意味着如果我们有一堆类型的字符,Stack[Char]
那么它就不能用作类型的整数堆栈Stack[Int]
。这将是不合理的,因为它将使我们能够在字符堆栈中输入真正的整数。总而言之,Stack[A]
只是Stack[B]
if的一个子类型,仅当它是B = A
。由于这可能非常严格,因此Scala提供了一种类型参数注释机制来控制泛型类型的子类型行为。
1.2、Scala的型变
Scala在高阶类型的使用中,有三种变化,是协变,逆变或不变。
class Foo[+A] // 协变 class Bar[-A] // 逆变 class Baz[A] // 不变
- 协变
A
通过使用注释,可以使泛型类的类型参数协变+A
。对于某些人来说class List[+A]
,A
协变意味着对于两种类型A
和B,
其中A
的子类B
,然后List[A]
的子类List[B]
。
abstract class Animal { def name: String } case class Cat(name: String) extends Animal case class Dog(name: String) extends Animal
Scala标准库有一个通用的不可变sealed abstract class List[+A]
类,其中type参数A
是协变的。这意味着List[Cat]
是List[Animal]的子类
, List[Dog]
也是List[Animal]的子类
。直观地说,猫的列表和狗的列表都是动物的列表是有道理的,你应该能够用它们中的任何一个替代它们List[Animal]
。如下:
object CovarianceTest extends App { def printAnimalNames(animal: List[Animal]): Unit ={ animal.foreach{animal=> println(animal.name) } } val cat:List[Cat]=List(Cat("catone"),Cat("Tom")) val dog:List[Dog]=List(Dog("dogone"),Dog("jurry")) printAnimalNames(cat) printAnimalNames(dog) }
方法printAnimalNames
将接受动物列表作为参数,并在新行上打印它们的名称。如果List[A]
不是协变的,则最后两个方法调用将不会编译,这将严重限制该printAnimalNames
方法的有用性。
- 逆变
A
可以使泛型类的类型参数成为逆变-A
。这会在类及其类型参数之间创建一个子类关系,该关系类似协变,但与之相反。也就是说,对于一些class Writer[-A]
,意味着,B是A的子类,Writer[B]
是Writer[A]的父类。
abstract class Printer[-A] { def print(value: A): Unit }
Printer[A]
是一个知道如何打印出某种类型的简单类A
。为特定类型定义一些子类:
class AnimalPrinter extends Printer[Animal]{ def print(animal: Animal): Unit = println("The animal's name is: " + animal.name) } class CatPrinter extends Printer[Cat] { def print(cat: Cat): Unit = println("The cat's name is: " + cat.name) }
具体应用如下:
object ContravarianceTest extends App { val myCat: Cat = Cat("Tom") def printMyCat(printer: Printer[Cat]): Unit = { printer.print(myCat) } val catPrinter: Printer[Cat] = new CatPrinter val animalPrinter: Printer[Animal] = new AnimalPrinter printMyCat(catPrinter) printMyCat(animalPrinter) }
- 输出如下:
- 不变
默认情况下,Scala中的泛型类是不变的。这意味着它们既不是协变也不是逆变。在以下示例的上下文中,Container
类是不变的。一个Container[Cat]
是不是一个Container[Animal]
,也不是恰好相反。
class Container[A](value: A) { private var _value: A = value def getValue: A = _value def setValue(value: A): Unit = { _value = value } }
看起来似乎Container[Cat]
应该是 Container[Animal]
,但是允许可变泛型类是协变的并不安全。
在这个例子中,非常重要的Container
是不变量。假设Container
实际上是协变的,可能会发生这样的事情:
val catContainer: Container[Cat] = new Container(Cat("Felix")) val animalContainer: Container[Animal] = catContainer animalContainer.setValue(Dog("Spot")) val cat: Cat = catContainer.getValue
1.3、上界
在Scala中,类型参数和抽象类型成员可能受类型绑定的约束。这种类型边界限制了类型变量的具体值,并可能揭示有关这些类型成员的更多信息。
结合上类型T <: A
声明类型变量T
是指类型的子类型A
。下面是一个示例,它演示了类的类型参数的上限类型PetContainer
:
abstract class Animal { def name: String } abstract class Pet extends Animal {} class Cat extends Pet { override def name: String = "Cat" } class Dog extends Pet { override def name: String = "Dog" } class Lion extends Animal { override def name: String = "Lion" } class PetContainer[P <: Pet](p: P) { def pet: P = p } val dogContainer = new PetContainer[Dog](new Dog) val catContainer = new PetContainer[Cat](new Cat)
该class PetContainer
采取的类型参数P
必须是的子类型Pet
。Dog
和Cat
都是子类,Pet
所以可以创建一个新的PetContainer[Dog]
和PetContainer[Cat]
。但是,如果尝试创建一个PetContainer[Lion]:
val lionContainer = new PetContainer[Lion](new Lion)
得到以下错误:
Error:(19, 7) type arguments [lab10.Lion] do not conform to class PetContainer's type parameter bounds [P <: lab10.Pet] val lionContainer = new PetContainer[Lion](new Lion)//Lion
它不是一个Pet的子类型
1.4、下界(较低类型边界)
上界将类型限制为另一种类型的子类型,但下界将类型声明为另一种类型的超类型。该术语B >: A
表示类型参数B
或抽象类型B
是指类型的超类型A
。在大多数情况下,A
将是类的类型参数,并且B
将是方法的类型参数。如下:
trait Node[+B] { def prepend(elem: B): Node[B] } case class ListNode[+B](h: B, t: Node[B]) extends Node[B] { def prepend(elem: B): ListNode[B] = ListNode(elem, this) def head: B = h def tail: Node[B] = t } case class Nil[+B]() extends Node[B] { def prepend(elem: B): ListNode[B] = ListNode(elem, this) }
该例实现了单链表。Nil
表示空元素(即空列表)。class List Node
是一个节点,它包含一个type B
(head
)元素和一个对list(tail
)其余部分的引用。它class Node
和它的亚型是协变的,因为我们有+B
。
但是,此程序无法编译,因为参数elem
in prepend
是type B
,我们声明了co变量。这不起作用,因为功能是禁忌在他们的参数类型变异和共同变种在他们的结果类型。
为了解决这个问题,我们需要翻转参数的类型的变化elem
在prepend
。我们通过引入一个U
具有B
较低类型边界的新类型参数来实现此目的。
trait Node[+B] { def prepend[U >: B](elem: U): Node[U] } case class ListNode[+B](h: B, t: Node[B]) extends Node[B] { def prepend[U >: B](elem: U): ListNode[U] = ListNode(elem, this) def head: B = h def tail: Node[B] = t } case class Nil[+B]() extends Node[B] { def prepend[U >: B](elem: U): ListNode[U] = ListNode(elem, this) }
现在可以做到以下几点:
trait Bird case class AfricanSwallow() extends Bird case class EuropeanSwallow() extends Bird val africanSwallowList= ListNode[AfricanSwallow](AfricanSwallow(), Nil()) val birdList: Node[Bird] = africanSwallowList birdList.prepend(new EuropeanSwallow)