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。

但是,此程序无法编译,因为参数elemin 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)

  

Scala实践11的更多相关文章

  1. CentOS7 安装 scala 2.11.1

    wget http://downloads.typesafe.com/scala/2.11.6/scala-2.11.6.tgz?_ga=1.61986863.2013247204.144801902 ...

  2. 【原创 Hadoop&Spark 动手实践 11】Spark Streaming 应用与动手实践

    [原创 Hadoop&Spark 动手实践 11]Spark Streaming 应用与动手实践 目标: 1. 掌握Spark Streaming的基本原理 2. 完成Spark Stream ...

  3. scala 2.11报错error: not found: type Application

    FROM: http://j-q-j.org/scala/scala-2-11-application-error.html 这两天学习scala,官网下载的最新版本2.11,书用的是<Prog ...

  4. Scala实践4

    一.数组 在Scala中,用()来访问元素,数组声明的语法格式如下 : var z:Array[String] = new Array[String](3) 或 var z = new Array[S ...

  5. 大数据系列修炼-Scala课程11

    接着昨天的list,也是学习集合的相关知识 ListBuffer.ArrayBuffer.Queue.stack相关操作 1.ListBuffer.ArrayBuffer代码实现:ListBuffer ...

  6. Scala实践8

    1.1继承类 使用extends关键字,在定义中给出子类需要而超类没有的字段和方法,或者重写超类的方法. class Person { var name = "zhangsan" ...

  7. Scala实践5

    一.Scala的层级 1.1类层级 Scala中,Any是所其他类的超类,在底端定义了一些有趣的类NULL和Nothing,是所有其他类的子类. 根类Any有两个子类:AnyVal和AnyRef.其中 ...

  8. ASP.NET-FineUI开发实践-11

    我用实例项目写了个子父页面传值,算是比较灵活的写法,可以把js提取出来写成包,然后调用,我先一步一步写,为有困难的朋友打个样. 先画个页面: 上面是个查询用的表单,底下是表格,内存分页,用到了VBox ...

  9. Socket编程实践(11) --epoll原理与封装

    常用模型的特点 Linux 下设计并发网络程序,有典型的Apache模型(Process Per Connection,PPC), TPC(Thread Per Connection)模型,以及 se ...

随机推荐

  1. Cookie内不能直接存入中文,cookie转码以及解码

    如果在cookie中存入中文,极易出现问题. js在存入cookie时,利用escape() 函数可对字符串进行编码, 用unescape()进行解码 顺序是先把cookie用escape()函数编码 ...

  2. H3C RIP基本配置

  3. H3C 单路径网络中环路产生过程(3)

  4. win10 uwp 发布旁加载自动更新

    在很多企业使用的程序都是不能通过微软商店发布,原因很多,其中我之前的团队开发了很久的应用,结果发现没有用户能从微软应用商店下载所以我对应用商店没有好感.但是作为一个微软粉丝,怎么能不支持 UWP 开发 ...

  5. linux 在 1 MB 之下的 ISA 内存

    一个最著名的 I/O 内存区是在个人计算机上的 ISA 范围. 这是在 640 KB(0xA0000)和 1 MB(0x100000)之间的内存范围. 因此, 它正好出现于常规内存 RAM 中间. 这 ...

  6. gulp插件使用

    //引入gulp组件 var gulp=require('gulp'); //创建任务 gulp.task('hello',function(){ console.log('hello'); }); ...

  7. linux中的文件类型标记方法

    在ls -l显示的详细信息中有以下信息: -rw-r--r-- drwxr-xr-x 一共10个字符,第一个字符表示文件类型,后面9个字符分成3组表示文件权限.前三个表示属主(拥有者)对文件的权限,中 ...

  8. Vue通讯

    vue组件通讯 #props传递数据 父->子 //父组件传递数据 <template> <Children :data="msg"></Chi ...

  9. python3.6中使用selenium + chromedriver访问Chrome浏览器时报错

    from selenium import webdriver from selenium.webdriver.chrome.options import Options if __name__ == ...

  10. sleep()与wait()区别

    1.所在类不一样:sleep在Thread类里面,wait在Object里面. 2.sleep需要抓异常,wait则不用. 3.sleep没有释放锁,wait释放锁. 4.sleep设置了释放时间,w ...